도서 요약 / / 2023. 1. 30. 21:19

리스코프 치환 원칙 : LSP(Liskov Substitution Principle)

이 내용은 udemy의 SOLID Design Principles를 정리한 내용입니다.

https://www.udemy.com/course/design-patterns-in-java-concepts-hands-on-projects/


이 원칙은 기반 클래스를 사용하는 곳에서 서브 클래스나 자식 클래스를 사용할 수 있어야 한다는 것을 말한다.
그리고 이렇게 교체할 때 프로그램의 속성을 수정하지 않아야 한다.

여기서 단순히 타입 기반 교체를 말하는 것은 아니다. Java에서 행위 기반의 서브타이핑을 말하는 것이다.

이것은 기반 클래스의 객체가 특정 행위를 제공하고 있고 기반 클래스의 객체가 행위가 변경되고 않고 자식 클래스로 교체된다는 것을 나타낸다.

예제

이제 Liskov Substitution Principle를 설명하는 Java 예제를 한번 보자. 이 예제를 한번 보면 완전히 이해할 수 있을 것이다.

@Getter
@Setter
@AllArgsConstructor
public class Rectangle {

    private int width;

    private int height;

    public int computeArea() {
        return width * height;
    }
}
public class Square extends Rectangle {

    public Square(int width, int height) {
        super(width, height);
    }

    @Override
    public void setWidth(int width) {
        setSide(width);
    }

    @Override
    public void setHeight(int height) {
        setSide(height);
    }

    private void setSide(int side) {
        super.setWidth(side);
        super.setHeight(side);
    }
}

Square 클래스는 Rectangle 클래스를 확장하고 있지만, Square는 모든 면의 길이가 동일하다.
Square 클래스는 Rectangle에 전달하는 폭과 높이가 동일한 길이의 단일 파라미터만 가지고 있다.

이제 Rectangle의 자식 클래스로써 Square 클래스를 만들 때, set 메소드를 사용한다. 누군가 특정 값으로 폭을 설정한다면 높이가 동일한 값으로 설정이 된다. 다른 값으로 폭과 높이를 가질 수가 없다.

다음은 Main 클래스이다.

public class Main {

    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle(10, 20);
        System.out.println(rectangle.computeArea());

        Square square = new Square(10);
        System.out.println(square.computeArea());

        useRectangle(rectangle);
    }

    private static void useRectangle(Rectangle rectangle) {
        rectangle.setHeight(20);
        rectangle.setWidth(30);
        assert rectangle.getHeight() == 20 : "Height Not equal to 20";
        assert rectangle.getWidth() == 30 : "Width Not equal to 30";
    }
}

useRectangle은 Rectangle을 인수로 받는 메소드이고 Rectangle과 Square를 모두 넘길 수 있다.

Main을 실행하기 전에 assert를 사용하기 때문에 VM arguments에 -ea플래그를 추가하자.

실행하면 다음과 같은 오류를 확인할 수 있다.

200
100
Exception in thread "main" java.lang.AssertionError: Height Not equal to 20
    at com.example.solid.lsp.before.Main.useRectangle(Main.java:19)
    at com.example.solid.lsp.before.Main.main(Main.java:13)

Square에서 setHeight로 30을 넘기면 width도 동일하게 30으로 변경이 된다.

그럼 어떻게 수정할 수 있을까?

유일한 방법은 이 연관관계를 끊어버리는 것이다. 수학적으로는 Square와 Rectangle이 의미가 있을지라도 객체지향 프로그래밍에서는 height와 width 메소드의 규약을 지키지 않기 때문에 유효하지 않다.

이 문제를 해결하기 위해서 할 수 있는 것은 Shape 인터페이스를 만드는 것이다.

public interface Shape {

    int computeArea();
}

그리고 Rectangle과 Square를 각각 구현한다.

@Getter
@Setter
@AllArgsConstructor
public class Rectangle implements Shape {

    private int width;

    private int height;

    public int computeArea() {
        return width * height;
    }
}
@Setter
public class Square implements Shape {

    private int side;

    public Square(int side) {
        this.side = side;
    }

    @Override
    public int computeArea() {
        return side * side;
    }
}
반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유