-
[SOLID] 리스코프 원칙 법칙이란 (Liskov Substitution Principle, LSP)공부 2021. 8. 16. 18:43
리스코프 치환 법칙은 SOLID 원칙에서 L에 해당하는 법칙입니다. 해당 법칙은 상위 타입의 객체를 하위 타입의 객체로 치환해도 동작에 문제가 없어야 합니다. 즉, B가 A의 자식일 때, A 타입을 사용하는 부분에서 B로 치환해도 문제없이 동작이 되어야 합니다.
아래에서 정사각형과 직사각형 예를 들어 설명합니다.
정사각형은 직사각형이 될 수 있지만 직사각형은 정사각형이 될 수 없습니다. 즉, 정사각형은 직사각형의 자식이라고 판단한 다음, 아래와 같이 코드를 작성합니다.
from dataclasses import dataclass, field @dataclass() class Rectangle: _width: int = field(init=False) _height: int = field(init=False) @property def width(self) -> int: return self._width @property def height(self) -> int: return self._height @width.setter def width(self, width: int): self._width = width @height.setter def height(self, height: int): self._height = height @property def area(self) -> int: return self._height * self._width class Square(Rectangle): @Rectangle.width.setter def width(self, number: int): self._width = number self._height = number @Rectangle.height.setter def height(self, number: int): self._width = number self._height = number
직사각형 클래스인 Rectangle 클래스를 정의한 다음 이를 상속하여 정사각형 클래스 Square 클래스를 만든 예시입니다. 정사각형의 경우, 넓이와 높이가 모두 같으므로 setter를 재정의 하였습니다.
이 코드를 사용한다면 리드코프 치환 원칙을 위배하게 됩니다.
def increase_height(__rect: Rectangle): __rect.height += 30 rect: Rectangle = Rectangle() rect.width = 10 rect.height = 20 increase_height(rect) print(rect.area == 500) # True rect2: Rectangle = Square() rect2.width = 10 rect2.height = 20 increase_height(rect2) print(rect2.area == 500) # False
부모 타입인 Rectangle에서 자식 타입인 Square로 변경하면 width와 height를 계산하는 식이 달라지므로 부모와 자식 클래스 간의 동작이 서로 다릅니다.
직사각형은 정사각형이지만 정사각형은 직사각형이다. 고로 직사각형 → 정사각형
이라는 논리적인 의미로 상속을 구현할 순 있지만 위 코드 예시를 통해 리스코프 치환 법칙에 위배되므로 상속 관계를 유지할 수 없습니다.만약 위 코드에서 Square 클래스일 때도 성립하게 만들기 위해 아래처럼 바꾼다면 이번엔 개방 폐쇄 원칙 (OCP)를 위배하게 됩니다.
def increase_height(__rect: Rectangle): if isinstance(__rect, Square): __rect = Rectangle() __rect.width = 10 __rect.height = 20 __rect.height += 30 return __rect rect2: Rectangle = Square() rect2.width = 10 rect2.height = 20 rect2 = increase_height(rect2) print(rect2.area == 500) # True
위의 increase_width 함수를 확장한 것이 아닌 이미 개발되어 있는 코드를 수정했기 때문에 위배를 하게 됩니다.
이러한 코드의 올바른 수정 방법은 아래와 같이 직사각형 → 정사각형 관계가 아닌 별개의 도형으로 취급을 해야 합니다.
from abc import ABCMeta, abstractmethod from dataclasses import dataclass class Shape(metaclass=ABCMeta): @property @abstractmethod def area(self): pass @dataclass() class Rectangle(Shape): _width: int _height: int @property def width(self) -> int: return self._width @property def height(self) -> int: return self._height @width.setter def width(self, width: int): self._width = width @height.setter def height(self, height: int): self._height = height @property def area(self) -> int: return self._height * self._width @dataclass() class Square(Shape): _width: int @property def width(self) -> int: return self._width @width.setter def width(self, number: int): self._width = number @property def area(self) -> int: return self._width ** 2 def increase_height(__rect: Rectangle): __rect.height += 30 def increase_width(__rect: Square): __rect.width += 30 rect: Rectangle = Rectangle(10, 20) increase_height(rect) print(rect.area == 500) # True square = Square(10) increase_width(square) print(square.area == 1600) # True
도형이라는 의미인 Shape 추상 클래스를 만든 다음, 직사각형과 정사각형 각각 해당 추상 클래스를 상속받아 구현한 케이스로 직사각형 → 정사각형 상속 관계를 없애버려 리스코프 치환 법칙 위배 문제를 해결했습니다.
요약
상속 관계를 구현할 때, 해당 법칙을 잘 상기하자
리스코프 치환 법칙을 보장하겠다고 개방 폐쇄 원칙을 위배하지 말자.