Juuunew 살아남기

[Spring] 객체지향 설계원칙 (SOLID) - 리스코프 치환 원칙 LSP 본문

Framework/Spring

[Spring] 객체지향 설계원칙 (SOLID) - 리스코프 치환 원칙 LSP

Juuunew 2022. 12. 11. 00:03

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

 

  • 상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.
  • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
  • 인터페이스가 명확해지고, 대체 가능성이 높아진다.
  • 부모 클래스를 상속하는 자식 클래스는 부모 클래스의 규약을 무시하거나 오버라이딩하는 것을 자제해야 한다.

가장 많이 보이는 정사각형과 직사각형 예제를 함께 살펴보자.

 

아래 직사각형 객체가 있다. 직사각형의 너비와 높이를 get/set 할 수 있으며, 너비와 높이를 곱하여 자신의 넓이를 구하는 메서드가 존재한다.

// 직사각형 Class
public class Rectangle {

    protected int width;
    protected int height;

    // 너비 반환 함수
    public int getWidth() {
        return width;
    }

    // 높이 반환 함수
    public int getHeight() {
        return height;
    }

	// 너비 지정
    public void setWidth(int width) {
        this.width = width;
    }

	// 높이 지정
    public void setHeight(int height) {
        this.height = height;
    }

	// 넓이 구하기
    public int getArea() {
        return width * height;
    }
}

 

다음으로 정사각형 객체가 있다. 정사각형은 직사각형의 한 종류로 볼 수 있으니 직사각형을 상속받아 객체를 구현할 수 있다.

 

// 직사각형 class 를 상속받은 정사각형 class
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());
    }
}

 

직사각형 객체와 이를 상속받은 정사각형 객체의 구현이 끝났다. 이제 두 객체의 넓이를 구하면서 살펴보도록 하자.

 

public class LspMain {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        rectangle.setWidth(10);
        rectangle.setHeight(5);

        System.out.println(rectangle.getArea());

        Rectangle square = new Square();
        square.setWidth(10);
        square.setHeight(5);

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

 

직사각형 (Rectangle)의 넓이는 가로가 10, 높이가 5 이므로 넓이는 10 * 5 = 50이 된다.

 

리스코프 치환 원칙에 따라, 자식 객체는 부모 객체를 완전히 대체할 수 있다고 했으니 정사각형 (Square)으로 대체하여 넓이를 구하면

동일한 결과인 50이 반환되어야 한다.

 

하지만 결과는 25가 반환된다. 왜일까?

 

정사각형은 가로와 세로의 길이가 같은 사각형이다. 따라서 Square class의 set메서드들을 살펴보면 할당받은 값으로 가로와 세로의 길이를 동일하게 할당해주게끔 구현되어있다.

 

위의 코드에서는 처음에 가로의 길이를 할당해주었다가 그다음에 세로의 길이 값을 할당해준다. 따라서 가로가 5, 세로가 5인 정사각형이 되었고, 넓이는 5 * 5 = 25 가 반환되게 된 것이다.

 

위의 코드들은 리스코프 치환 원칙에 위배되는 코드들이다.


위배되는 코드들을 올바른 상속과 구현을 통해 리스코프 치환 원칙을 준수하는 코드들로 변경하여보자.

 

먼저 직사각형도 정사각형도 아닌 부모 클래스가 될 사각형 클래스를 만들어보자.

 

public class Shape {
    protected int width;
    protected int height;

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = 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);
    }
}

 

직사각형은 가로길이와 세로 길이가 다르니 두 가지의 매개 변수를, 정사각형은 동일하니 한 가지의 매개 변수를 가진다.

 

두 객체의 넓이는 다르지만 상속 관계가 끊어졌으므로 리스코프 치환 원칙에 위배하지않게 된다.

 


리스코프 치환 원칙자식 객체는 부모 객체를 완전히 대체하여도 아무런 문제가 없도록 해야 한다.

 

위의 직사각형과 정사각형의 케이스처럼 올바르지 못한 상속관계는 제거하고, 부모 객체의 동작을 완벽하게 대체할 수 있는 관계만 상속하도록 설계하여야 한다.

 

💡 공부 중 정리하는 내용이므로 부족한 부분이 있을 수 있습니다.

 

💡 참고자료

 

스프링 핵심 원리 - 기본편 (인프런 김영한)

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

참고 블로그

 

[OOP] 객체지향 5원칙(SOLID) - 리스코프 치환 원칙 (Liskov Subsitution Principle) - 𝝅번째 알파카의 개발

리스코프 치환 원칙은 부모 객체와 이를 상속한 자식 객체가 있을 때 부모 객체를 호출하는 동작에서 자식 객체가 부모 객체를 완전히 대체할 수 있다는 원칙이다. 객체지향 언어에선 객체의 상

blog.itcode.dev