본문 바로가기

소프트웨어

[소프트웨어/SOLID] 리스코브 치환 법칙(LSP)


LSP(Liskov-Substitution Principle)

자식클래스는 부모 클래스에서 가능한 행위를 모두 수행할 수 있어야한다.

위의 정의는 자식클래스가 부모클래스를 대체할 수도 있어야 하고 일반화 관계를 의미한다. 일반화 관계는 is a kind of관계이다. 원숭이와 포유류는 is a kind of관계가 맞다.

포유류는 아래와 같은 조건을 만족해야한다.

  • 알을 낳지 않고 새끼를 낳을 것
  • 젖을 먹여 새끼를 키우고 폐를 통해 호흡할 것
  • 체온이 일정한 정온 동물이며 털이나 두꺼운 피부로 덮혀 있을 것

원숭이는 위와 같은 원칙을 모두 만족하지만 오리너구리는 알을 낳아 번식하는 동물이므로 만족하지 않는다. 따라서 오리구리는 포유류에 만족하지 않아, LSP에 위배된다.

public class Bag {
    private int price;

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

위와 같은 Bag 클래스에서 아래와 같은 법칙을 만족하는데

[new Bag().setPrice(1000)].getPrice() == 1000;

위의 코드 중 [ ]는 내부에 사용된 객체를 반환한다, 즉 new Bag()을 반환하는데, 결과적으로 set한 값이 get값으로 이상잆이 반환되는지 확인하는 코드가 된다.

LSP룰을 만족하려면 아래와 같이 기존 코드의 수정이나 값을 수정없이 그대로 사용되어야한다.

public class DiscountableBag extends Bag {
    private double discountedRate = 0;

    public void setDiscountedRate(double discountedRate) {
        this.discountedRate = discountedRate;
    }

    public int getDiscountedPrice() {
        return (int) (this.getPrice() * (1-discountedRate));
    }
}

그럼 여전히 [new Bag().setPrice(1000)].getPrice() == 1000가 만족하는 것을 알 수 있다. 왜냐하면 getPrice()에는 전혀 수정을 가하지 않았기 떄문이다. 하지만 여전히 아래와 같이 코드를 쓰는 사람은 굉장히 많다.

public class WrongDiscountableBag extends Bag {
    private double discountedRate = 0;

    public void setDiscountedRate(double discountedRate) {
        this.discountedRate = discountedRate;
    }

    @Override
    public int getPrice() {
        return (int) (super.getPrice() * (1-discountedRate));
    }
}

이 같은 경우 기존의 getPrice()를 Override 했으므로 [new Bag().setPrice(1000)].getPrice() == 1000 만족하지 않아 LSP원칙에 위배된다.

아래와 같은 피터코드 상속 규칙과 동일한 원칙이라고 생각해도 된다.

서브 클래스가 슈퍼 클래스의 책임을 무시하거나 재정의하지 않고 확장을 수행한다.