SOLID 이란?
클린 코딩 디자인중 하나인 SOLID 원칙에 대해서 천천히 알아보자.
- S: 단일 책임 원칙
- O: 개방/폐쇄의 원칙 👈 현위치
- L: 리스코프 치환 원칙
- I: 인터페이스 분리 법칙
- D: 의존성 역전 원칙
개방/폐쇄 원칙
간단히 말해서 확장 가능하고, 새로운 요구사항이나 도메인 변화에 잘 적응하는 코드를 작성 해야 한다.
예제 내용 : 다른 시시템에서 발생하는 이벤트를 분류하는 기능
안티 페턴을 통한 이해
class Event:
def __init__(self, raw_data):
self.raw_data = raw_data
class UnknownEvent(Event):
"""데이터만으로 식별할 수 없는 이벤트"""
pass
class LoginEvent(Event):
"""로그인 이벤트"""
pass
class LogoutEvent(Event):
"""로그아웃 이벤트"""
pass
class SystemMonitor:
"""시스템에서 발생한 이벤트 분류"""
def __init__(self, event_data):
self.event_data = event_data
def identify_event(self):
"""이벤트 식별"""
if (
self.event_data["before"]["session"] == 0
and self.event_data["after"]["session"] == 1
):
return LoginEvent(self.event_data)
elif (
self.event_data["before"]["session"] == 1
and self.event_data["after"]["session"] == 0
):
return LogoutEvent(self.event_data)
return UnknownEvent(self.event_data)
l1 = SystemMonitor({"before": {"session": 0}, "after": {"session": 1}})
print(l1.identify_event().__class__.__name__)
l2 = SystemMonitor({"before": {"session": 1}, "after": {"session": 0}})
print(l2.identify_event().__class__.__name__)
l3 = SystemMonitor({"before": {"session": 1}, "after": {"session": 1}})
print(l3.identify_event().__class__.__name__)
identify_evnet 함수를 눈여겨 보자
넘어온 데이터 중 "before" -> "session"가 0이고 "after" -> "session"이 1이면 Login을 호출하고 반대 조건인 경우는 Logout을 호출한다. 둘다 해당하지 않을 경우 UnkonwnEvent 객체를 호출한다.
해당 코드의 실행 결과는 다음과 같다.
이 디자인에는 몇가지 문제점이 있다.
- 이벤트 유형을 결정하는 논리가 일체형으로 중화 집중화 된다는 점이다.
이벤트가 늘어날수록 메서드도 커질 것이므로 추 후 매우 큰 메서드가 될 수 있다. - 새로운 이벤트를 추가할 때마다 메서드를 수정해야 한다.(물론 elif 명령어 체인은 가독성에 안좋다)
확장성을 가진 이벤트 시스템으로 리펙토링
SystemMonitor 클래스를 추상적인 이벤트와 협력하도록 변경하고 이벤트에 대응하는 개별 로직은 각 이벤트 클레스에 위힘 하는 것으로 리펙토링을 진행 하겠다.
class Event:
def __init__(self, raw_data):
self.raw_data = raw_data
@staticmethod
def meets_condition(event_data: dict):
return False
class UnknownEvent(Event):
"""데이터만으로 식별할 수 없는 이벤트"""
pass
class LoginEvent(Event):
"""로그인 이벤트"""
@staticmethod
def meets_condition(event_data: dict):
return (
event_data["before"]["session"] == 0 and event_data["after"]["session"] == 1
)
class LogoutEvent(Event):
"""로그아웃 이벤트"""
@staticmethod
def meets_condition(event_data: dict):
return (
event_data["before"]["session"] == 1 and event_data["after"]["session"] == 0
)
class SystemMonitor:
"""시스템에서 발생한 이벤트 분류"""
def __init__(self, event_data):
self.event_data = event_data
def identify_event(self):
"""이벤트 식별"""
for event_class in Event.__subclasses__():
try:
if event_class.meets_condition(self.event_data):
return event_class(self.event_data)
except KeyError:
continue
return UnknownEvent(self.event_data)
추상화를 통해 이뤄지고 있음을 주목하자 모든 클레스에서 meets_condition 메서드를 구현해서 다형성을 보장 하였다.
또한 __subclasses 매서드를 사용해 다른 이벤트가 늘어나더라도 수정할 필요 없는 함수를 구현 함으로 폐쇄되어 있게 구현 되었다.
간단하게 이벤를 추가해 보는 코드를 추가해 보자.
...
class TransactionEvent(Event):
"""거래 이벤트"""
@staticmethod
def meets_condition(event_data: dict) -> bool:
return event_data["after"].get("transaction") is not None
...
l1 = SystemMonitor({"before": {"session": 0}, "after": {"session": 1}})
print(l1.identify_event().__class__.__name__)
l2 = SystemMonitor({"before": {"session": 1}, "after": {"session": 0}})
print(l2.identify_event().__class__.__name__)
l3 = SystemMonitor({"before": {"session": 1}, "after": {"session": 1}})
print(l3.identify_event().__class__.__name__)
l4 = SystemMonitor({"after": {"transaction": "0001"}})
print(l4.identify_event().__class__.__name__)
SystemMonitor에 있는 identify_event 함수는 변경하지 않는 체로 TransactionEvent를 확장 시킨 모습을 몰 수 있다.
identify_event 함수는 폐쇄 적이고 Event 추가에 관해서는 개방되어 있다.
Django에서 개방/폐쇄 정책을 어떻게 활용 하면 좋을까?
글을 작성하는 시점에서 아직 어떻게 Django에서 SOLID를 적용해야 할지 연구중에 있습니다.
공부를 계속해 가다가 발견하는 점이 있으면 글을 수정해서 업데이트 하도록 하겠습니다.
또한 읽으신 분들도 예시나 사례를 남겨 주시면 포스팅에 많은 도움이 될거 같습니다.
'Python' 카테고리의 다른 글
[Python] SOLID_인터페이스 분리(ISP) (0) | 2023.02.09 |
---|---|
[Python] SOLID_리스코프 치환 원칙(LSP) (0) | 2023.02.08 |
[Python] SOLID_단일 책임 원칙(SRP) (0) | 2023.02.06 |
[Python] 이터러블 객체 (0) | 2023.02.01 |
[Python] Context Manager 일반적으로 쓰이는 예시 (2) | 2023.01.31 |