[디자인 패턴] 컴퍼짓(Composite) 패턴이란
오늘 공부할 디자인 패턴은 컴퍼짓 패턴입니다.
브릿지 패턴 포스트에서 말씀드렸다시피 컴퍼짓 패턴을 잘못 알고 있었기에 이번 공부도 정말 뿌듯합니다.
충분히 그리고 열심히 공부하고 있다고 생각했었는데 이렇게 부족함을 느끼게 되어서 정말 다행인 것 같습니다.
그만큼 내용을 정리하면서 얻는 게 많은 것 같습니다. 이제 본격적으로 공부해볼까요!
1. 컴퍼짓 패턴이란
컴퍼짓 패턴은 동일한 추상화가 여러 계층에 걸쳐서 적용될 수 있을 때 사용합니다. 많이 예로 드는 경우가, 파워포인트의 도형, 게임 속 오브젝트들인데요.
파워포인트를 이용해서 도형들을 그린 다음에 이들을 다시 그룹화 시킬 수 있다는 것 아시나요? 중요한 점은 그룹화한 도형도 일반 도형과 똑같이 제어할 수 있습니다. 컴퍼짓 패턴을 이용하면 사용자에게 일관성 있는 인터페이스를 제공할 수 있습니다.
2. 활용하기 좋은 순간
- 추상화를 재귀적으로 적용시켜야 할 때
3. 예시
이번에는 차량 검사를 예시로 들어볼 거에요! 우리가 자주 애용하던 Car에 inspect 라는 메소드를 추가할 겁니다. 그런데 차량 검사라고 하면 브레이크도 검사하고, 엔진도 검사하고 이것 저것 많이 검사하죠? 그리고 기능이 추가할수록 검사할 내용이 많아질 겁니다.
그럴 때마다 Car.inspect 에서 해당 부품의 inspect 메소드를 호출하는 코드를 추가해줘야 할까요?
예시 코드처럼 Composite 패턴을 적용하면 그런 문제를 해결할 수 있습니다. 모든 부품들을 검사해야 한다는 책임을 PartCompositeInterface가 가지게 되었습니다.
하지만 단점도 보입니다. 하위 부품이 필요 없는 경우에도 add_part, remove_part를 구현해야 하는 것이죠. 그리고 add_part, remove_part를 통해서 하위 부품들을 "잘" 관리해야 하는 것이죠.
이런 점들을 유의하며 컴퍼짓 패턴을 이용해보세요!
## car.py
from abc import ABCMeta, abstractmethod
class Car:
def __init__(self, *, part_composite: PartCompositeInterface):
self.part_composite = part_composite
def inspect(self):
self.part_composite.inspect()
## part_composite.py
from abc import ABCMeta, abstractmethod
class PartCompositeInterface(metaclass=ABCMeta):
def __init__(self):
self.part_map = {}
@abstractmethod
def inspect(self):
pass
@abstractmethod
def add_part(self, part: PartInterface):
pass
@abstractmethod
def remove_part(self, id: str):
pass
## k3_part_composite.py
class K3PartComposite(PartCompositeInterface):
def __init__(self):
super().__init__()
self.id = "k3-part"
def inspect(self):
for part_id, part in self.part_map.items():
part.inspect()
def add_part(self, part: PartInterface):
self.part_map[part.get_id()] = part
def remove_part(self, id: str):
try:
del self.part_map[id]
except:
print("invalid part id")
## a1_break_part.py
import uuid
class A1BreakPart(PartCompositeInterface):
def __init__(self):
self.id = str(uuid.uuid4())
def get_id(self) -> str:
return self.id
def inspect(self):
"""
1. get hardware information
2. raise exception error when error detected
"""
pass
def add_part(self, part: PartInterface):
pass
def remove_part(self, id: str):
pass
## main.py
def __init__ == "__main__":
k3_part_composite = K3PartComposite()
k3_part_composite.add(A1BreakPart())
car = Car(part_composite=k3_part_composite)
car.inspect()