JAVA

[JAVA] LSP (리스코프 치환 원칙)

준몽쓰 2025. 4. 7. 23:45

■ LSP

- 부모 객체와 자식 객체가 있을 때 부모 객체를 호출하는 동작에서 자식 객체가 부모 객체를 완전히 대체할 수 있다는 원칙

- 자식 클래스는 부모 클래스가 정의한 계약을 위반하지 않고 부모 클래스의 기능을 확장하거나 수정할 수 있어야 한다

 

 

■ LSP 위반한 예제

// 부모 클래스
class Bird {
    public void fly() {
        System.out.println("새는 날 수 있다.");
    }
}

// 자식 클래스
class chicken extends Bird {
    @Override
    public void fly() {
        throw new ChickenException("닭은 날지 못한다.");
    }
}

// 테스트 클래스
public class LSPViolationTest {
    public static void main(String[] args) {
        Bird bird = new Chicken();
        bird.fly();  // 예외 발생: 닭은 날지 못한다.
    }
}

 

- Bird 클래스의 fly() 메서드는 모든 새들이 날 수 있따는 가정 하에 동작하는 메서드

- Chicken은 날 수 없지만, Bird 클래스를 상속받고 fly() 메서드를 오버라이드하면서 예외를 던진다.

- Bird 타입의 객체로 Chicken 객체를 대체하려 할 때 예외가 발생하고, 이는  LSP 원칙에 어긋난다.

- Chicken는 Bird 가 기대하는 fly() 동작을 제대로 구현하지 않고 예외를 던지기 때문에 대체할 수 없는 객체

 

■ LSP 준수한 예제

// 부모 클래스
class Bird {
    public void eat() {
        System.out.println("새는 먹는 중이다.");
    }
}

// 날 수 있는 새 클래스
class FlyingBird extends Bird {
    public void fly() {
        System.out.println("날 수 있는 새는 날고있다.");
    }
}

// 날 수 없는 새 클래스
class Chicken extends Bird {
    // 닭은 날 수 없으므로 fly() 메서드를 제공하지 않음
    public void run() {
        System.out.println("닭은 뛰는 중이다.");
    }
}

// 테스트 클래스
public class Main {
    public static void main(String[] args) {
        // FlyingBird 객체
        Bird bird1 = new FlyingBird();
        bird1.eat();
        ((FlyingBird) bird1).fly();  // FlyingBird는 날 수 있음

        // Chicken 객체
        Bird bird2 = new Chicken();
        bird2.eat();
        ((Chicken) bird2).run();  // 닭은 날 수 없고, 대신 달릴 수 있음
    }
}

- Chicken는 Bird 클래스를 상속 받지만, fly() 메서드를 제공하지 않고 자신의 특성에 맞는 run() 메서드를 제공한다. 

- Chicken는 Bird 클래스를 대체할 수 있지만, 날 수 없는 새로서 제대로 기능 수행

- FlyingBird는 날 수 있는 새들을 다루기 때문에 fly() 메서드를 제공하며 날 수 있는 새들이 Brid 객체로 다뤄져도 문제 발생 안함

- Chicken는 fly() 메서드를 호출하지 않으며 run() 메서드를 호출한다. 각 객체는 자신에게 맞는 기능만 수행