Skip to content

decorator-pattern

TL;DR

  • 데코레이터 패턴은 객체에 동적으로 새로운 기능을 추가할 수 있게 해주는 디자인 패턴임.
  • 이 패턴은 상속을 사용해 기능을 확장하는 것보다 더 유연한 방법을 제공함.
    • 런티임에 객체에 새로운 기능을 추가할 수 있음.
    • 기능 조합에 유연성
    • 기능의 독립적인 추가 및 제거
    • 클래스의 폭발 문제 해결
Code Example
from abc import ABC, abstractmethod

# Component 인터페이스
class VisualComponent(ABC):
    @abstractmethod
    def draw(self):
        pass

# ConcreteComponent
class TextView(VisualComponent):
    def draw(self):
        print("Drawing a text view")

# Decorator - 데코레이터는 감싸는 대상의 인터페이스를 구현해야 하며,
# 추가기능을 구현 후 self._component 를 통해서 나머지 처리를 위임한다.
class Decorator(VisualComponent):
    def __init__(self, component: VisualComponent):
        self._component = component

    def draw(self):
        self._component.draw()

# ConcreteDecoratorA
class BorderDecorator(Decorator):
    def draw(self):
        super().draw()
        self._draw_border()

    def _draw_border(self):
        print("Adding a border")

# ConcreteDecoratorB
class ScrollDecorator(Decorator):
    def draw(self):
        super().draw()
        self._draw_scroll()

    def _draw_scroll(self):
        print("Adding scroll bars")

# 클라이언트 코드
if __name__ == "__main__":
    # 기본 TextView 객체 생성
    text_view = TextView()

    # 텍스트 뷰에 테두리를 추가
    bordered_text_view = BorderDecorator(text_view)

    # 테두리가 추가된 텍스트 뷰에 스크롤을 추가
    scrollable_bordered_text_view = ScrollDecorator(bordered_text_view)

    # 최종 객체의 draw 메서드를 호출
    scrollable_bordered_text_view.draw()

설명

  • VisualComponent는 기본 인터페이스 또는 추상 클래스임.
  • TextView는 기본 구현체로, 단순히 텍스트를 출력하는 역할을 함.
  • DecoratorVisualComponent를 상속받아 draw 메서드를 호출하도록 하고, 데코레이터들이 이 클래스를 상속받아 추가 기능을 구현할 수 있도록 함.
  • BorderDecoratorScrollDecorator는 각각 테두리와 스크롤 기능을 추가하는 데코레이터임.
  • 최종적으로, scrollable_bordered_text_view 객체를 사용하여 draw 메서드를 호출하면 기본 텍스트 뷰에 테두리와 스크롤이 추가된 출력이 나옴.

데코레이터 패턴이란?

  • 데코레이터 패턴은 객체에 새로운 책임을 동적으로 추가할 수 있게 해주는 패턴임.
  • 서브클래스를 생성하지 않고도 객체에 기능을 추가할 수 있어 더 융통성이 있음.
  • Wrapper 패턴이라고도 불림.

사용 동기

  • 전체 클래스에 새로운 기능을 추가할 필요는 없지만, 특정 객체에만 기능을 추가하고 싶을 때 유용함.
  • 상속을 통해 기능을 추가할 수도 있지만, 이 방법은 유연성이 부족할 수 있음.
  • 예를 들어, GUI의 스크롤 기능이나 테두리 추가 기능이 필요할 때 데코레이터 패턴을 사용할 수 있음.

구조와 참여자

  • Component: 동적으로 추가할 수 있는 서비스를 가질 가능성이 있는 객체에 대한 인터페이스임.
  • ConcreteComponent: 실제로 추가적인 서비스가 정의될 필요가 있는 객체임.
  • Decorator: Component에 대한 참조자를 관리하며, Component의 인터페이스를 구현함.
  • ConcreteDecorator: Component에 새롭게 추가할 서비스를 실제로 구현하는 클래스임.

데코레이터 패턴의 장단점

  • 장점
    1. 단순 상속보다 설계의 융통성을 증가시킬 수 있음.
    2. 클래스 계층 구조의 상위 클래스에 불필요한 기능이 누적되는 것을 방지할 수 있음.
  • 단점
    1. 장식자와 그 장식자가 꾸미는 객체가 동일하지 않을 수 있음. 이는 식별자 이슈를 야기할 수 있음.
    2. 작은 규모의 객체들이 많이 생기므로 관리 비용이 증가할 수 있음.

데코레이터 패턴의 활용성

  • 동적으로 또는 투명하게, 다른 객체에 영향을 주지 않고 개별 객체에 기능을 추가할 수 있음.
  • 필요에 따라 제거할 수 있는 책임을 추가할 때 사용함.
  • 상속을 사용한 방법이 실질적이지 못할 때 유용함. 특히, 많은 독립적인 확장이 가능할 때 이를 모두 상속으로 해결하면 클래스의 수가 폭발적으로 증가할 수 있음.

데코레이터 패턴 구현 시 고려사항

  • 데코레이터는 반드시 구성 요소의 인터페이스를 만족시켜야 함.
  • ConcreteDecorator는 동일한 부모 클래스를 상속받아야 함.
  • 데코레이터 클래스를 추상 클래스로 정의할 필요가 없는 경우 이를 생략할 수 있음.
  • Component 클래스는 가능한 가볍게 유지하는 것이 좋음.

관련 패턴

  • 데코레이터 패턴은 적응자 (Adapter) 패턴과 유사함. 적응자가 인터페이스를 변경하는 것이라면, 데코레이터는 객체의 책임과 행동을 변경함.
  • 복합체 (Composite) 패턴과도 관련이 있음. 다만, 복합체는 객체의 합성을 목표로 하지만, 데코레이터는 객체에 새로운 행동을 추가하는 데 목적이 있음.
  • 전략 (Strategy) 패턴과는 객체를 변화시키는 두 가지 대안임. 데코레이터는 객체의 겉모습을 변화시키고, 전략 패턴은 객체의 내부 로직을 변화시킴.