데코레이터(Decorator)
디자인 패턴 중에서 데코레이터(Decorator)에 대해서 알아보자. 데코레이터 패턴은 GoF가 제시한 구조 패턴(Structural Pattern) 중의 하나로 어떤 한 클래스의 인스턴스 동작을 변경하지 않고, 새로운 인스턴스에 동작을 다이나믹하게 추가하는 패턴이다.
데코레이터 패턴은 기능이 확장될 때 서브클래싱(subclassing)의 대안으로 사용할 수 있다. 그리고 데코레이터 패턴은 객체지향 5대 원칙(SOLID) 중 OCP(Open-Close Principle)에 해당한다. OCP는 확장에는 개방되고, 변경에는 폐쇄한다는 원칙인데, 이는 변경을 위한 비용은 가능한 줄이고, 확장을 위한 비용은 가능한 극대화해야 한다는 의미이다. 즉, 기존의 소프트웨어를 쉽게 확장해서 재사용할 수 있어야 한다는 것이다.
기본 개념
데코레이터 패턴에는 ConcreateComponent 클래스와 Decorator 클래스로 구현되는 Component 라는 인터페이스가 있다.
Component: 추상 클래스(abstract class)로 ConcreateComponent 클래스와 Decorator 클래스의 슈퍼 클래스이다. 이 두 클래스에서 사용할 표준 메소드를 정의한다.
ConcreateComponent: Component 클래스의 서브클래스로 표준 메소드를 구체화한다.
Decorator: 추상 클래스(abstract class)로 Component 클래스의 서브클래스이자 ConcreateDecorator 클래스의 슈퍼 클래스이다.
ConcreateDecorator: Decorator 클래스의 서브클래스로 기능을 추가하여 Component를 장식하는 클래스이다. 추가되는 기능에 따라 ConcreateDecoratorA(), ConcreateDecoratorB() 등 계속 확장할 수 있다.
구현
데코레이터 패턴의 대부분의 예제는 커피를 이용하고 있다. 커피에는 아메리카노, 카페모카 등 커피의 기본가격이 있고, 샷추가, 휘핑 추가에 따라 가격이 달라짐으로 데코레이터 패턴을 구현하기 매우 적합하다.
from abc import ABC, abstractmethod
class Coffee(ABC):
"""Interface"""
@abstractmethod
def cost(self):
pass
class Americano(Coffee):
"""ConcreateComponent"""
def cost(self):
return 2000
class Moka(Coffee):
"""ConcreateComponent"""
def cost(self):
return 4000
class CoffeeDecorator(Coffee, ABC):
"""Decorator"""
def __init__(self, coffee):
self._coffee = coffee
def cost(self):
return self._coffee.cost()
class Shot(CoffeeDecorator):
"""ConcreateDecorator"""
def cost(self):
return self._coffee.cost() + 500
class Whip(CoffeeDecorator):
"""ConcreateDecorator"""
def cost(self):
return self._coffee.cost() + 1000
if __name__ == '__main__':
o1 = Americano()
o2 = Moka()
o3 = Whip(Shot(Moka()))
print(o1.cost()) # 2000
print(o2.cost()) # 4000
print(o3.cost()) # 5500
코드에서 o3 객체를 보면 Moka를 Whip(), Shot()으로 래핑(wrapping)하여 장식하고 있는것을 알 수 있다.
장점
- 런타임시에 특정 객체에 대해서만 추가 기능을 확장할 수 있다.
- 서브클래스는 컴파일시에 기능이 추가되어 모든 인스턴스에 영향을 주지만 데코레이터는 특정 객체에 대해서만 기능을 확장한다.
- 추가되는 기능에 대해 유연하게 대처할 수 있다.
단점
- 래핑의 수가 많을수록 복잡도가 증가한다.
- 기능이 추가될수록 가독성이 떨어진다.