리스코프 치환 원칙은 상속 관계에 있는 클래스 간에 호환성을 보장하는 객체지향 설계 원칙이다.
자식 클래스가 부모 클래스의 역할을 완전히 대체할 수 있을 만큼 기대된 동작이 보장되어야 한다는 의미이다.
예제
# 부모
class Event:
def __init__(self, event_name):
self.event_name = event_name
def event_execute(self):
pass
# 자식 1
class MinorEvent(Event):
def __init__(self, event_name):
super().__init__(event_name)
def event_execute(self):
print(f"Minor event. {self.event_name}")
# 자식 2
class MajorEvent(Event):
def __init__(self, event_name):
super().__init__(event_name)
def event_execute(self):
print(f"Major event. {self.event_name}")
# 핸들러(클라이언트)
class EventHandler:
def __init__(self, event: Event):
self.event = event
# Event 클래스의 인터페이스 활용
def handling(self):
self.event.event_execute()
if __name__ == '__main__':
event = MinorEvent("Event 1")
handler = EventHandler(event)
handler.handling()
자식 클래스(Minor, Major)는 부모 클래스(Event)를 상속받아 event_execute 메서드를 객체의 역할에 따라 오버라이드했다.
위와 같이 부모 클래스로부터 상속된 인터페이스에 맞춰서 또 다른 이벤트 클래스를 생성해 나간다면 클라이언트 입장에서는 부모의 인터페이스에 따라 생성된 이벤트 클래스이기 때문에 객체를 활용함에 있어서 어렵지 않게 사용할 수 있게 된다.
그래서 부모의 인터페이스에 따라 생성된 이벤트 객체는 이벤트 핸들러에서 Minor, Major 둘 중 어느 이벤트가 오든 상관없이 동작할 수 있게 된다.
그렇다면 만약 위반한 경우에는 어떨까?
위반한 경우
class Event:
def __init__(self, event_name):
self.event_name = event_name
def event_execute(self):
pass
class MinorEvent(Event):
def __init__(self, event_name):
super().__init__(event_name)
def event_execute(self):
print(f"Minor event. {self.event_name}")
class MajorEvent(Event):
def __init__(self, event_name):
super().__init__(event_name)
def event_execute(self):
print(f"Major event. {self.event_name}")
class CriticalEvent(Event):
def __init__(self, event_name, event_info): # 부모와 다르게 event_info 인자값을 하나 더 받는다.
super().__init__(event_name)
def event_execute(self, depth): # 이벤트 처리 깊이를 나타내는 depth 인자값도 받아서 처리한다.
print(f"Critical event. {self.event_name}, depth: {depth}")
class EventHandler:
def __init__(self, event: Event):
self.event = event
def handling(self):
# if isinstance(self.event, CriticalEvent): . . . # 크리티컬 이벤트에 대한 예외처리
self.event.event_execute() # depth 인자값을 주지 않아 에러 발생
if __name__ == '__main__':
# event = MinorEvent("Event 1")
# event = Critical("Event 5", {"Bandwidth": "1000M", "MSG_YN": "Y"}) 수정 필요
event = CriticalEvent("Event 5") # event_info 인자값을 주지 않아 에러 발생
handler = EventHandler(event)
handler.handling()
위반한 사례를 보면 새로 생성한 클래스(CriticalEvent)는 부모 클래스의 성격과는 다르게 동작하기 때문에 클라이언트에서는 소스 수정이 불가피하다.
내부 로직이 더 크고 복잡했다면 발생할 사이드 이펙트는 더욱 커졌을 것이다.