원하는 데이터 객체를 만들기 위해 많은 비용을 들이지 말자. 다음 예를 보자
class TransactionalPolicy(collections.UserDict):
"""잘못된 상속의 예"""
def change_in_policy(self, customer_id, **new_policy_data):
self[customer_id].update(**new_policy_data)
이 클래스는 고객의 정책에 접근해 정책을 바꾼다는 목적에는 부합할 수 있으나, 상속의 안티패턴을 보여주고 있다.
- 계층 구조가 잘못됐다.
기본 클래스에서 새 클래스를 만드는 것은 개념적으로 확장되고 세부적인 것이다라는 것을 뜻한다. 하지만 해당 데이터 객체는 Dict를 상속하면서 Dict의 개념을 확장했다기 보단 필요한 몇몇 부분(고객 데이터를 접근하는 부분)만 사용했다는 느낌을 가지게 된다. - 결합력이 강하다.
이 클래스에는 pop() 또는 items()와 같은 메서드가 필요하지 않지만 포함되어 있다. 덕타이핑으로 볼 때 해당 오리는 필요없는 부위가 있는 이상한 오리다.
이것이 구현 객체를 도메인 객체와 혼합할 때 발생하는 문제이다. dict는 특정 유형의 작업에 적합한 객체 또는 데이터 구조로서 다른 데이터 구조와 마찬가지로 트레이드오프가 있다. TransactionPolicy는 특정 도메인의 정보를 나타내는 것이므로 해결하려는 문제의 일부분에 사용되는 엔티티여야 한다.
올바른 해결책은 컴포지션을 사용하는 것이다.
컴포지션이란?
다른 클래스의 인스턴스를 자신의 인스턴스 변수로 가지고 있는 것이다.
class TransactionalPolicy:
"""컴포지션을 사용한 리팩토링 예제"""
def __init__(self, policy_data, **extra_data):
self._data = {**policy_data, **extra_data}
def change_in_policy(self, customer_id, **new_policy_data):
self._data[customer_id].update(**new_policy_data)
def __getitem__(self, customer_id):
return self._data[customer_id]
def __len__(self):
return len(self._data)
이 방법은 개념적으로 정확할 뿐만 아니라 확장성도 뛰어나다.
'Python' 카테고리의 다른 글
가상파일시스템 pyfakefs 를 통해 테스트 코드 작성하기 (0) | 2023.04.24 |
---|---|
Poweshell 환경에서 poetry 와 pyenv로 가상환경 세팅하기 (0) | 2023.03.15 |
스택프레임, 빅오 (0) | 2023.03.01 |
결합과 추상화 - 3 (0) | 2023.02.21 |
결합과 추상화 - 2 (0) | 2023.02.13 |