알고리즘/SOLID

L - 리스코프 치환 원칙(Liskov Substitution Principle) [기본]

Master potato 2024. 4. 14. 16:00

 

리스코프 치환 원칙은 상속 관계에 있는 클래스 간에 호환성을 보장하는 객체지향 설계 원칙이다.

자식 클래스가 부모 클래스의 역할을 완전히 대체할 수 있을 만큼 기대된 동작이 보장되어야 한다는 의미이다.

 

 

예제
# 부모
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)는 부모 클래스의 성격과는 다르게 동작하기 때문에 클라이언트에서는 소스 수정이 불가피하다.

내부 로직이 더 크고 복잡했다면 발생할 사이드 이펙트는 더욱 커졌을 것이다.