흰제비갈매기 2024. 6. 10. 14:57

LSP(Liscov Substituion Principle)

 

S가 T의 하위 유형이면 프로그램에서 자료형 T의 객체는 해당 프로그램의 원하는 속성을 변경하지 않고 자료형 S의 객체로 교체(치환)할 수 있어야 한다.

 

리스코프 치환 원칙이란 부모 객체와 자식 객체가 있을 때 부모 객체를 호출하는 동작에서 자식 객체가 부모 객체를 완전히 대체할 수 있다는 원칙이다.

 

OOP에서 상속이 일어나면, 하위 타입인 자식 객체는 상위 타입인 부모 객체의 특성을 가지며, 그 특성을 토대로 확장할 수 있다.

리스코프 치환 원칙은 올바른 상속을 위해, 자식 객체의 확장이 부모 객체의 방향을 온전히 따르도록 권고하는 원칙이다.



LSP를 위반한 예제 코드

직사각형, 정사각형 관계를 코드로 보자

public class Rectangle {

    public int width;
    public int height;

    // 너비 반환, Width Getter
    public int getWidth() {
        return width;
    }
    // 너비 할당, Width Setter
    public void setWidth(int width) {
        this.width = width;
    }

    // 높이 반환, Height Getter
    public int getHeight() {
        return height;
    }
    // 높이 할당, Height Setter
    public void setHeight(int height) {
        this.height = height;
    }

    //직사각형 넓이 반환 함수
    public int getArea() {
        return width * height;
    }
}

public class Square extends Rectangle{
    @Override
    public void setWidth(int Width) {
        super.setWidth(width);
        super.setHeight(getWidth());
    }

    @Override
    public void setHeight(int height) {
        super.setHeight(height);
        super.setWidth(getHeight());
    }
}

Rectangle 객체를 상속 받은 Square 클래스에서는 정사각형의 너비와 높이가 같다는 특징을 구현했다. 너비와 높이 둘 중 하나를 입력해도
나머지 값이 일치되도록 메서드를 override 해주었다.

아래는 Rectangle 클래스이다. 높이를 5, 너비를 10으로 설정했다.

public class Main {
    public static void main(String[] args) {

        Rectangle rectangle = new Rectangle();
        rectangle.setHeight(5);
        rectangle.setWidth(10);

        System.out.println(rectangle.getArea()); // 50
    }
}

다음은 Square 테스트이다.

리스코프 치환 원칙은 부모 객체를 호출하는 동작에서 자식 객체가 부모 객체를 완전히 대체할 수 있다는 원칙이다.

 

그렇다면 Rectangle 클래스에서의 테스트와 같은 값을 할당했을 때, 당연히 완전히 같은 결과를 반환해야 한다.

public class Main {
    public static void main(String[] args) {

        Rectangle square = new Square();

        square.setWidth(10);
        square.setHeight(5);


        System.out.println(square.getArea()); // 25
    }
}

아래는 50이 아니라 25가 반환되었다. 가장 마지막에 수행된 setHeight(5)가 객체의 너비와 높이를 모두 5로 할당했기에
25가 나오는 것은 당연하다.



위의 코드 둘다 Rectangle 자료형으로 인스턴스를 받고 있다. 하지만 Rectangle과 Square 클래스의 동작이 전혀
다르다는 사실을 알 수 있다.

 

이는 바로 정사각형이 직사각형을 상속 받는 것이 올바른 상속 관계가 아니라는 것을 의미한다.
자식 객체가 부모 객체의 역할을 완전히 대체하지 못한다는 의미이다.

 

이렇게 잘못된 객체를 상속하거나 올바르게 확장하지 못할 경우, 겉으로 보기엔 정상적이지만 올바른 객체라고 할 수는 없다.

 

위 같은 코드가 리스코프 치환 원칙을 위배하는 코드이다.



LSP를 준수한 코드

올바르게 성립하는 상속 관계를 구현한 코드이다.

public class Shape {

    public int width;
    public int height;

    // 너비 반환, Width Getter
    public int getWidth() {
        return width;
    }
    // 너비 할당, Width Setter
    public void setWidth(int width) {
        this.width = width;
    }

    // 높이 반환, Height Getter
    public int getHeight() {
        return height;
    }
    // 높이 할당, Height Setter
    public void setHeight(int height) {
        this.height = height;
    }

    // 사각형 넓이 반환
    public int getArea() {
        return width * height;
    }
}

//직사각형 클래스
public class Rectangle extends Shape {

    public Rectangle(int width, int height) {
        setWidth(width);
        setHeight(height);
    }
}

//정사각형 클래스
public class Square extends Shape{

    public Square(int length) {
        setWidth(length);
        setHeight(length);
    }
}

public class Main {
    public static void main(String[] args) {

        Shape rectangle = new Rectangle(10, 5);
        Shape square = new Square(5);

        System.out.println(rectangle.getArea()); // 50
        System.out.println(square.getArea()); // 25
    }
}



정리

리스코프 치환 원칙은 상속되는 객체는 반드시 부모 객체를 완전히 대체할 수 있어야 한다고 권고한다.

 

첫 번째의 직사각형 클래스를 상속받은 정사각형 객체 예제처럼, 올바르지 못한 상속관계는 제거하고 부모 객체의 동작을 완벽히 대체할 수 있는 관계만 상속하도록 코드를 설계해야 한다.

출처

https://velog.io/@harinnnnn/OOP-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-5%EB%8C%80-%EC%9B%90%EC%B9%99SOLID-%EB%A6%AC%EC%8A%A4%EC%BD%94%ED%94%84-%EC%B9%98%ED%99%98-%EC%9B%90%EC%B9%99-LSP