맨위로가기

데코레이터 패턴

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

데코레이터 패턴은 객체의 동작을 동적으로 추가하는 디자인 패턴으로, 상속 대신 합성을 사용하여 유연성을 높인다. 구성 요소, 구체적인 구성 요소, 데코레이터, 구체적인 데코레이터로 구성되며, UML 클래스 다이어그램과 시퀀스 다이어그램으로 표현된다. 자바, C++ 등 다양한 언어로 구현 가능하며, GUI 툴킷, I/O 스트림, 웹 개발, 게임 개발 등 광범위하게 활용된다. 어댑터 패턴, 퍼사드 패턴 등 다른 패턴과 비교하여 사용되며, 기능 확장에 대한 합성 방식을 지원하지만, 코드 복잡성과 객체 관리의 어려움이라는 단점도 존재한다.

더 읽어볼만한 페이지

  • 소프트웨어 디자인 패턴 - 모델-뷰-컨트롤러
    모델-뷰-컨트롤러(MVC)는 소프트웨어 디자인 패턴으로, 응용 프로그램을 모델, 뷰, 컨트롤러 세 가지 요소로 분리하여 개발하며, 사용자 인터페이스 개발에서 데이터, 표현 방식, 사용자 입력 처리를 분리해 유지보수성과 확장성을 높이는 데 기여한다.
  • 소프트웨어 디자인 패턴 - 스케줄링 (컴퓨팅)
    스케줄링은 운영 체제가 시스템의 목적과 환경에 맞춰 작업을 관리하는 기법으로, 장기, 중기, 단기 스케줄러를 통해 프로세스를 선택하며, CPU 사용률, 처리량 등을 기준으로 평가하고, FCFS, SJF, RR 등의 알고리즘을 사용한다.
데코레이터 패턴
개요
유형구조적
목적객체에 동적으로 새로운 책임을 추가
동의어래퍼(Wrapper)
디자인 패턴 종류객체
관계데코레이터 패턴은 어댑터 패턴, 전략 패턴과 밀접한 관련이 있으며, 때로는 이들과 함께 사용됨.
의도주어진 객체에 추가적인 책임을 동적으로 결합함.
유연한 대안으로 서브클래싱을 제공함.
문제객체에 대한 책임을 추가하는 방법은 무엇인가?
상속을 사용하는 것은 유연하지 않음. 상속은 책임을 정적으로 할당하고 컴파일 시간에만 변경할 수 있기 때문임.
해결책기본 객체와 동일한 인터페이스를 구현하는 데코레이터 객체를 생성함.
데코레이터 객체는 기본 객체를 감싸고 추가적인 책임을 제공함.
구조Component: 데코레이터와 함께 사용될 수 있는 객체의 인터페이스를 정의함.
ConcreteComponent: Component 인터페이스를 구현하고 데코레이터에 의해 꾸며질 객체를 정의함.
Decorator: Component 인터페이스를 구현하고 Component 객체에 대한 참조를 유지함. 또한 Component 인터페이스를 데코레이터에 위임함.
ConcreteDecorator: 데코레이터에 추가적인 책임을 추가함.
참여자Component: 인터페이스를 정의함.
ConcreteComponent: 인터페이스를 구현함.
Decorator: Component에 대한 참조를 유지하고 Component 인터페이스를 구현함.
ConcreteDecorator: 책임을 추가함.
협업클라이언트는 ConcreteComponent 객체를 래핑하는 ConcreteDecorator 객체를 생성하여 책임을 추가함.
결과데코레이터 패턴은 객체에 추가적인 책임을 동적으로 추가할 수 있게 함.
데코레이터 패턴은 단일 책임 원칙을 준수함. 각 데코레이터는 특정 책임을 추가하는 데만 집중함.
데코레이터 패턴은 개방/폐쇄 원칙을 준수함. 새로운 데코레이터를 추가하여 기존 코드를 수정하지 않고도 객체에 새로운 기능을 추가할 수 있음.
장점상속 대신 객체 구성을 사용하여 런타임에 객체에 책임을 추가하는 데 더 큰 유연성을 제공함.
서브클래스가 증가하는 것을 피할 수 있음.
단일 책임 원칙과 개방/폐쇄 원칙을 준수함.
단점객체를 데코레이팅하는 것은 복잡성을 증가시킬 수 있음. 특히 데코레이터가 많은 경우 더욱 그러함.
데코레이터가 많으면 디버깅하기 어려울 수 있음.
클라이언트가 객체의 정체성을 많이 필요로 하는 경우 문제가 될 수 있음.
활용 예시Java I/O 스트림
GUI 프레임워크
게임 개발
구현Component 인터페이스를 정의함.
ConcreteComponent 클래스를 구현함.
Decorator 추상 클래스를 구현함.
ConcreteDecorator 클래스를 구현함.
예제Java I/O 스트림: BufferedInputStream, DataInputStream
GUI 프레임워크: JScrollPane
관련 패턴어댑터 패턴
복합체 패턴
전략 패턴
구조
데코레이터 패턴 UML 클래스 다이어그램
데코레이터 패턴 UML 클래스 다이어그램
로마자 표기
영어Decorator Pattern
일본어Dekorētā patān (デコレータパターン)

2. 구조



데코레이터 패턴은 다음과 같은 주요 구성 요소로 이루어진다.[4]


  • '''Component (구성 요소)''': 실제 객체와 데코레이터 객체가 공통으로 가져야 할 인터페이스를 정의한다.
  • '''ConcreteComponent (구체적인 구성 요소)''': Component 인터페이스를 구현하는 클래스로, 실제 기능을 가진 객체이다.
  • '''Decorator (데코레이터)''': Component 객체에 대한 참조를 유지하고, Component와 동일한 인터페이스를 구현한다. 클라이언트에게 투명하게 동작하며, 요청을 Component 객체에 전달하기 전후에 추가 기능을 수행한다.
  • '''ConcreteDecorator (구체적인 데코레이터)''': Decorator 클래스를 상속받아 실제 추가 기능을 구현하는 클래스들이다.


데코레이터 디자인 패턴의 예시 UML 클래스 및 시퀀스 다이어그램.


UML 클래스 다이어그램에서 추상 클래스 `Decorator`는 데코레이션된 객체(`Component`)에 대한 참조(`component`)를 유지하며, 모든 요청을 해당 객체(`component.operation()`)로 전달한다.[7] 이로 인해 `Decorator`는 `Component`의 클라이언트에게 투명하게(보이지 않게) 동작한다. 하위 클래스(`Decorator1`, `Decorator2`)는 `Component`에 추가되어야 하는 추가적인 동작(`addBehavior()`)을 구현한다(요청을 전달하기 전/후).[7]

데코레이터 UML 클래스 다이어그램


시퀀스 다이어그램은 실행 시간 상호 작용을 보여준다.[7] `Client` 객체는 `Decorator1` 및 `Decorator2` 객체를 통해 작동하여 `Component1` 객체의 기능을 확장한다. `Client`는 `Decorator1`에 `operation()`을 호출하고, `Decorator1`은 `Decorator2`에 요청을 전달한다. `Decorator2`는 `Component1`에 요청을 전달한 후 `addBehavior()`를 수행하고 `Decorator1`로 반환하며, `Decorator1`은 `addBehavior()`를 수행하고 `Client`로 반환한다.

2. 1. Component (구성 요소)

Component(구성 요소)는 실제 객체와 데코레이터 객체가 공통으로 가져야 할 인터페이스를 정의한다.[4]

```java

// the Window interface

interface Window {

public void draw(); // draws the Window

public String getDescription(); // returns a description of the Window

}

// implementation of a simple Window without any scrollbars

class SimpleWindow implements Window {

public void draw() {

// draw window

}

public String getDescription() {

return "simple window";

}

}

```

```cpp

#include

using namespace std;

/* Component (interface) */

class Widget {

public:

virtual void draw() = 0;

virtual ~Widget() {}

};

/* ConcreteComponent */

class TextField : public Widget {

private:

int width, height;

public:

TextField( int w, int h ){

width = w;

height = h;

}

void draw() {

cout << "TextField: " << width << ", " << height << '\n';

}

};

```

위의 코드에서 Java 예제에서는 `Window` 인터페이스가, C++ 예제에서는 `Widget` 추상 클래스가 Component 역할을 한다. 이들은 `draw()`라는 추상 메서드를 가지고 있으며, 이를 통해 윈도우를 그리거나 위젯을 표현한다. `SimpleWindow`와 `TextField` 클래스는 각각 `Window`와 `Widget` 인터페이스를 구현하는 구체적인 Component 클래스이다.

2. 2. ConcreteComponent (구체적인 구성 요소)

java

class SimpleWindow implements Window {

public void draw() {

// draw window

}

public String getDescription() {

return "simple window";

}

}

```

`SimpleWindow` 클래스는 `Window` 인터페이스를 구현(implements영어)한다. `draw()` 메서드는 창을 그리는 기능을, `getDescription()` 메서드는 "simple window"라는 문자열을 반환하여 창을 설명하는 기능을 제공한다. 이 클래스는 스크롤바와 같은 추가 기능이 없는 가장 기본적인 창을 나타낸다.

2. 3. Decorator (데코레이터)

''데코레이터''[4] 디자인 패턴은 잘 알려진 23가지 디자인 패턴 중 하나이다. 이 패턴은 반복적인 디자인 문제를 해결하고, 유연하고 재사용 가능한 객체 지향 소프트웨어를 설계하는 방법을 제시한다. 즉, 구현, 변경, 테스트 및 재사용이 더 쉬운 객체를 만드는 방법을 설명한다.

데코레이터 패턴은 기능을 확장하기 위해 서브클래싱을 사용하는 대신 더 유연한 대안을 제공한다. 서브클래싱을 사용하면 서로 다른 서브클래스가 서로 다른 방식으로 클래스를 확장한다. 그러나 이러한 확장은 컴파일 타임에 클래스에 고정되며 런타임에는 변경할 수 없다. 데코레이터 패턴은 런타임에 객체에 기능을 동적으로 추가하거나 제거할 수 있게 해준다. 이는 다음과 같은 특징을 가진 `Decorator` 객체를 정의함으로써 가능하다.

  • 확장(데코레이션)되는 객체(`Component`)의 인터페이스를 구현하고, 모든 요청을 해당 객체에 전달하여 투명하게 동작한다.
  • 요청을 전달하기 전이나 후에 추가적인 기능을 수행한다.


이러한 방식을 통해 여러 `Decorator` 객체를 사용하여 런타임에 객체의 기능을 동적으로 확장할 수 있다.[5]

데코레이터 패턴은 설계 시점에 몇 가지 기초 작업을 하면 특정 객체의 기능을 정적으로 확장하거나, 경우에 따라 런타임에 같은 클래스의 다른 인스턴스와 독립적으로 확장할 수 있다. 이는 원본 클래스를 래핑하는 새로운 ''데코레이터'' 클래스를 설계하여 구현된다. 이 래핑은 다음 단계를 통해 수행된다.

1. 원본 ''Component'' 클래스를 ''Decorator'' 클래스로 서브클래싱한다(UML 다이어그램 참조).

2. ''Decorator'' 클래스에 ''Component'' 포인터를 필드로 추가한다.

3. ''Decorator'' 클래스에서 ''Component'' 포인터를 초기화하기 위해 ''Component''를 ''Decorator'' 생성자에 전달한다.

4. ''Decorator'' 클래스에서 모든 ''Component'' 메서드를 ''Component'' 포인터로 전달한다.

5. ConcreteDecorator 클래스에서 동작을 수정해야 하는 모든 ''Component'' 메서드를 재정의한다.

이 패턴은 여러 데코레이터가 서로 위에 쌓여, 재정의된 메서드에 매번 새로운 기능을 추가할 수 있도록 설계되었다.

데코레이터와 원본 클래스 객체는 공통적인 기능 세트를 공유한다는 점을 유의해야 한다. 이전 다이어그램에서 operation() 메서드는 데코레이션된 버전과 데코레이션되지 않은 버전 모두에서 사용할 수 있었다.

데코레이션 기능(예: 메서드, 속성 또는 기타 멤버)은 일반적으로 데코레이터와 데코레이션된 객체가 공유하는 인터페이스, 믹스인 (일명 트레이트) 또는 클래스 상속에 의해 정의된다.

데코레이터 패턴은 서브클래스의 대안이다. 서브클래싱은 컴파일 타임에 동작을 추가하며, 변경 사항은 원본 클래스의 모든 인스턴스에 영향을 미친다. 반면, 데코레이션은 선택된 객체에 대해 런타임에 새로운 동작을 제공할 수 있다.[5]

이러한 차이점은 기능을 확장하는 몇 가지 '독립적인' 방법이 있을 때 가장 중요해진다. 일부 객체 지향 프로그래밍 언어에서는 런타임에 클래스를 만들 수 없으며, 설계 시점에 어떤 조합의 확장이 필요할지 예측하는 것이 일반적으로 불가능하다. 이는 모든 가능한 조합에 대해 새로운 클래스를 만들어야 함을 의미한다. 반대로, 데코레이터는 런타임에 생성되는 객체이며, 사용 시마다 결합될 수 있다. Java와 .NET 프레임워크의 I/O 스트림 구현은 데코레이터 패턴을 사용한다.[5]

예를 들어, 윈도우 시스템의 윈도우를 생각해 보자. 윈도우 내용의 스크롤을 허용하기 위해, 필요에 따라 가로 또는 세로 스크롤바를 추가할 수 있다. 윈도우는 ''Window'' 인터페이스의 인스턴스로 표현되며, 이 클래스에는 스크롤바를 추가하는 기능이 없다고 가정한다. 이를 제공하는 ''ScrollingWindow'' 하위 클래스를 만들거나, 기존 ''Window'' 객체에 이 기능을 추가하는 ''ScrollingWindowDecorator''를 만들 수 있다. 이 시점에서는 두 가지 솔루션 모두 괜찮을 것이다.

이제, 윈도우에 테두리를 추가하는 기능도 원한다고 가정해 보자. 마찬가지로, 원래의 ''Window'' 클래스에는 지원 기능이 없다. ''ScrollingWindow'' 하위 클래스는 새로운 종류의 윈도우를 효과적으로 만들었기 때문에 이제 문제를 제기한다. 많은 윈도우에는 테두리 지원을 추가하고 싶지만, ''모든'' 윈도우에 추가하고 싶지는 않다면, ''WindowWithBorder'' 및 ''ScrollingWindowWithBorder'' 등의 하위 클래스를 만들어야 한다. 이 문제는 새로운 기능이나 윈도우 하위 유형을 추가할 때마다 더 심각해진다. 데코레이터 솔루션의 경우, 새로운 ''BorderedWindowDecorator''가 생성된다. ''ScrollingWindowDecorator'' 또는 ''BorderedWindowDecorator''의 조합은 기존 윈도우를 장식할 수 있다. 모든 윈도우에 기능을 추가해야 하는 경우, 기본 클래스를 수정할 수 있다. 반면에, 때로는 (예: 외부 프레임워크를 사용하는 경우) 기본 클래스를 수정하는 것이 불가능하거나, 법적으로 허용되지 않거나, 편리하지 않을 수 있다.

데코레이터 패턴의 방침은 기존 객체를 새로운 `Decorator` 객체로 감싸는 것이다. 그 방법으로, `Decorator`의 생성자 인수로 감쌀 대상인 `Component` 객체를 읽어 들여, 생성자 내부에서 해당 객체를 멤버로 설정하는 것이 일반적이다.

데코레이터 패턴은 기존 클래스를 확장할 때 클래스 상속의 대체 수단으로 사용된다. 상속이 컴파일 시에 기능을 확장하는 데 반해, 데코레이터 패턴은 프로그램 실행 시에 기능 추가를 하는 점이 다르다.

2. 4. ConcreteDecorator (구체적인 데코레이터)

Decorator 클래스를 상속받아 실제 추가 기능을 구현하는 클래스들이다. '구체적인 데코레이터'로 번역될 수 있다.

예를 들어, 윈도우 시스템의 윈도우에 가로 또는 세로 스크롤바를 추가하거나, 윈도우에 테두리를 추가하는 기능을 구현할 수 있다.

위의 UML 클래스 다이어그램에서, 추상 클래스 `Decorator`의 하위 클래스(`Decorator1`, `Decorator2`)는 `Component`에 추가되어야 하는 추가적인 동작(`addBehavior()`)을 구현한다 (요청을 전달하기 전/후).[7]

시퀀스 다이어그램은 실행 시간 상호 작용을 보여준다. `Client` 객체는 `Decorator1` 및 `Decorator2` 객체를 통해 작동하여 `Component1` 객체의 기능을 확장한다.[7]

`Client`는 `Decorator1`에 `operation()`을 호출하고, `Decorator1`은 `Decorator2`에 요청을 전달한다. `Decorator2`는 `Component1`에 요청을 전달한 후 `addBehavior()`를 수행하고 `Decorator1`로 반환하며, `Decorator1`은 `addBehavior()`를 수행하고 `Client`로 반환한다.[7]

아래는 구체적인 데코레이터 클래스들의 예시이다.



// the first concrete decorator which adds vertical scrollbar functionality

class VerticalScrollBarDecorator extends WindowDecorator {

public VerticalScrollBarDecorator (Window decoratedWindow) {

super(decoratedWindow);

}

public void draw() {

drawVerticalScrollBar();

decoratedWindow.draw();

}

private void drawVerticalScrollBar() {

// draw the vertical scrollbar

}

public String getDescription() {

return decoratedWindow.getDescription() + ", including vertical scrollbars";

}

}

// the second concrete decorator which adds horizontal scrollbar functionality

class HorizontalScrollBarDecorator extends WindowDecorator {

public HorizontalScrollBarDecorator (Window decoratedWindow) {

super(decoratedWindow);

}

public void draw() {

drawHorizontalScrollBar();

decoratedWindow.draw();

}

private void drawHorizontalScrollBar() {

// draw the horizontal scrollbar

}

public String getDescription() {

return decoratedWindow.getDescription() + ", including horizontal scrollbars";

}

}





/* ConcreteDecoratorA */

class BorderDecorator : public Decorator {

public:

BorderDecorator( Widget* w ) : Decorator( w ) { }

void draw() {

Decorator::draw();

cout << " BorderDecorator" << '\n';

}

};

/* ConcreteDecoratorB */

class ScrollDecorator : public Decorator {

public:

ScrollDecorator( Widget* w ) : Decorator( w ) { }

void draw() {

Decorator::draw();

cout << " ScrollDecorator" << '\n';

}

};


2. 5. UML 클래스 다이어그램



위의 UML 클래스 다이어그램에서, 추상 클래스 `Decorator`는 데코레이션된 객체(`Component`)에 대한 참조(`component`)를 유지하며 모든 요청을 해당 객체(`component.operation()`)로 전달한다. 이로 인해 `Decorator`는 `Component`의 클라이언트에게 투명하게(보이지 않게) 된다.

하위 클래스(`Decorator1`, `Decorator2`)는 `Component`에 추가되어야 하는 추가적인 동작(`addBehavior()`)을 구현한다(요청을 전달하기 전/후).[7]

시퀀스 다이어그램은 실행 시간 상호 작용을 보여준다. `Client` 객체는 `Decorator1` 및 `Decorator2` 객체를 통해 작동하여 `Component1` 객체의 기능을 확장한다. `Client`는 `Decorator1`에 `operation()`을 호출하고, `Decorator1`은 `Decorator2`에 요청을 전달한다. `Decorator2`는 `Component1`에 요청을 전달한 후 `addBehavior()`를 수행하고 `Decorator1`로 반환하며, `Decorator1`은 `addBehavior()`를 수행하고 `Client`로 반환한다.[7]

3. 동작 방식

클라이언트는 Component 인터페이스를 통해 객체를 사용한다. Decorator는 Component 객체를 감싸고, 클라이언트의 요청을 Component 객체에 전달한다.[4] ConcreteDecorator는 요청을 전달하기 전후에 추가적인 작업을 수행하여 기능을 확장한다. 여러 개의 ConcreteDecorator를 중첩하여 기능을 조합할 수 있다.[5]

위의 UML 클래스 다이어그램에서 추상 클래스 `Decorator`는 데코레이션된 객체(`Component`)에 대한 참조(`component`)를 유지하며 모든 요청을 해당 객체(`component.operation()`)로 전달한다. 이로 인해 `Decorator`는 `Component`의 클라이언트에게 투명하게(보이지 않게) 된다.[7]

하위 클래스(`Decorator1`, `Decorator2`)는 `Component`에 추가되어야 하는 추가적인 동작(`addBehavior()`)을 구현한다(요청을 전달하기 전/후).

시퀀스 다이어그램은 실행 시간 상호 작용을 보여준다. `Client` 객체는 `Decorator1` 및 `Decorator2` 객체를 통해 작동하여 `Component1` 객체의 기능을 확장한다.

`Client`는 `Decorator1`에 `operation()`을 호출하고, `Decorator1`은 `Decorator2`에 요청을 전달한다. `Decorator2`는 `Component1`에 요청을 전달한 후 `addBehavior()`를 수행하고 `Decorator1`로 반환하며, `Decorator1`은 `addBehavior()`를 수행하고 `Client`로 반환한다. 데코레이터 패턴의 방침은 기존 객체를 새로운 `Decorator` 객체로 감싸는 것이다. 그 방법으로, `Decorator`의 생성자 인수로 감쌀 대상인 `Component` 객체를 읽어 들여, 생성자 내부에서 해당 객체를 멤버로 설정하는 것이 일반적이다.

데코레이터 패턴은 기존 클래스를 확장할 때 클래스 상속의 대체 수단으로 사용된다. 상속이 컴파일 시에 기능을 확장하는 데 반해, 데코레이터 패턴은 프로그램 실행 시에 기능 추가를 하는 점이 다르다.

4. 예제

다음은 C++98 이전의 구현을 기반으로 한 코드이다.[7]

```cpp

#include

#include

// 음료 인터페이스

class Beverage {

public:

virtual void drink() = 0;

virtual ~Beverage() = default;

};

// 장식될 수 있는 음료

class Coffee : public Beverage {

public:

virtual void drink() override {

std::cout << "커피를 마시는 중";

}

};

class Soda : public Beverage {

public:

virtual void drink() override {

std::cout << "소다를 마시는 중";

}

};

// 음료 데코레이터

class BeverageDecorator : public Beverage {

public:

BeverageDecorator() = delete;

BeverageDecorator(std::unique_ptr component_)

: component(std::move(component_))

{

}

virtual void drink() = 0;

protected:

void callComponentDrink() {

if (component) {

component->drink();

}

}

private:

std::unique_ptr component;

};

// 우유 데코레이터

class Milk : public BeverageDecorator {

public:

Milk(std::unique_ptr component_, float percentage_)

: BeverageDecorator(std::move(component_))

, percentage(percentage_)

{

}

virtual void drink() override {

callComponentDrink();

std::cout << ", 우유는 " << percentage << "%";

}

private:

float percentage;

};

// 얼음 데코레이터

class IceCubes : public BeverageDecorator {

public:

IceCubes(std::unique_ptr component_, int count_)

: BeverageDecorator(std::move(component_))

, count(count_)

{

}

virtual void drink() override {

callComponentDrink();

std::cout << ", 얼음 " << count << " 개";

}

private:

int count;

};

// 설탕 데코레이터

class Sugar : public BeverageDecorator {

public:

Sugar(std::unique_ptr component_, int spoons_)

: BeverageDecorator(std::move(component_))

, spoons(spoons_)

{

}

virtual void drink() override {

callComponentDrink();

std::cout << ", 설탕 " << spoons << " 스푼";

}

private:

int spoons = 1;

};

int main() {

// 소다에 얼음 3개, 설탕 1스푼 추가

std::unique_ptr soda = std::make_unique();

soda = std::make_unique(std::move(soda), 3);

soda = std::make_unique(std::move(soda), 1);

soda->drink();

std::cout << std::endl;

// 커피에 얼음 16개, 우유 3%, 설탕 2스푼 추가

std::unique_ptr coffee = std::make_unique();

coffee = std::make_unique(std::move(coffee), 16);

coffee = std::make_unique(std::move(coffee), 3.);

coffee = std::make_unique(std::move(coffee), 2);

coffee->drink();

std::cout << std::endl;

return 0;

}

```

위 코드의 출력 결과는 다음과 같다.

```cpp

커피를 마시는 중, 얼음 3 개, 설탕 1 스푼

커피를 마시는 중, 얼음 16 개, 우유는 3%, 설탕 2 스푼

```

전체 예제는 [https://godbolt.org/z/s848nWozP godbolt page]에서 테스트할 수 있다.

다음은 파이썬 예제이며, https://wiki.python.org/moin/DecoratorPattern 파이썬 위키 - DecoratorPattern에서 가져왔다. 이 예제는 객체에 여러 동작을 동적으로 추가하기 위해 데코레이터를 파이프라인하는 방법을 보여준다.[8]

```python

"""

0-255의 값을 갖는 10x10 그리드 세상에서 데코레이터를 시연합니다.

"""

import random

def s32_to_u16(x):

if x < 0:

sign = 0xF000

else:

sign = 0

bottom = x & 0x00007FFF

return bottom | sign

def seed_from_xy(x, y):

return s32_to_u16(x) | (s32_to_u16(y) << 16)

class RandomSquare:

def __init__(s, seed_modifier):

s.seed_modifier = seed_modifier

def get(s, x, y):

seed = seed_from_xy(x, y) ^ s.seed_modifier

random.seed(seed)

return random.randint(0, 255)

class DataSquare:

def __init__(s, initial_value=None):

s.data = [initial_value] * 10 * 10

def get(s, x, y):

return s.data[(y * 10) + x] # yes: these are all 10x10

def set(s, x, y, u):

s.data[(y * 10) + x] = u

class CacheDecorator:

def __init__(s, decorated):

s.decorated = decorated

s.cache = DataSquare()

def get(s, x, y):

if s.cache.get(x, y) == None:

s.cache.set(x, y, s.decorated.get(x, y))

return s.cache.get(x, y)

class MaxDecorator:

def __init__(s, decorated, max):

s.decorated = decorated

s.max = max

def get(s, x, y):

if s.decorated.get(x, y) > s.max:

return s.max

return s.decorated.get(x, y)

class MinDecorator:

def __init__(s, decorated, min):

s.decorated = decorated

s.min = min

def get(s, x, y):

if s.decorated.get(x, y) < s.min:

return s.min

return s.decorated.get(x, y)

class VisibilityDecorator:

def __init__(s, decorated):

s.decorated = decorated

def get(s, x, y):

return s.decorated.get(x, y)

def draw(s):

for y in range(10):

for x in range(10):

print "%3d" % s.get(x, y),

print

# 이제 데코레이터 파이프라인을 구축합니다:

random_square = RandomSquare(635)

random_cache = CacheDecorator(random_square)

max_filtered = MaxDecorator(random_cache, 200)

min_filtered = MinDecorator(max_filtered, 100)

final = VisibilityDecorator(min_filtered)

final.draw()

```

'''참고:''' 데코레이터 패턴 (또는 위 예제와 같은 파이썬에서의 이 디자인 패턴 구현)은 파이썬 데코레이터라는 파이썬 언어 기능과는 혼동해서는 안 된다.[8]

다음은 Ruby 코드 예제이다.

```ruby

class AbstractCoffee

def print

puts "가격: #{cost}; 재료: #{ingredients}"

end

end

class SimpleCoffee < AbstractCoffee

def cost

1.0

end

def ingredients

"커피"

end

end

class WithMilk < SimpleDelegator

def cost

__getobj__.cost + 0.5

end

def ingredients

__getobj__.ingredients + ", 우유"

end

end

class WithSprinkles < SimpleDelegator

def cost

__getobj__.cost + 0.2

end

def ingredients

__getobj__.ingredients + ", 스프링클"

end

end

coffee = SimpleCoffee.new

coffee.print

coffee = WithMilk.new(coffee)

coffee.print

coffee = WithSprinkles.new(coffee)

coffee.print

```

위 코드의 출력 결과는 다음과 같다.

```

가격: 1.0; 재료: 커피

가격: 1.5; 재료: 커피, 우유

가격: 1.7; 재료: 커피, 우유, 스프링클

```

다음은 C# 코드 예제이다.

```csharp

namespace WikiDesignPatterns;

public interface IBike

{

string GetDetails();

double GetPrice();

}

public class AluminiumBike : IBike

{

public double GetPrice() =>

100.0;

public string GetDetails() =>

"Aluminium Bike";

}

public class CarbonBike : IBike

{

public double GetPrice() =>

1000.0;

public string GetDetails() =>

"Carbon";

}

public abstract class BikeAccessories : IBike

{

private readonly IBike _bike;

public BikeAccessories(IBike bike)

{

_bike = bike;

}

public virtual double GetPrice() =>

_bike.GetPrice();

public virtual string GetDetails() =>

_bike.GetDetails();

}

public class SecurityPackage : BikeAccessories

{

public SecurityPackage(IBike bike):base(bike)

{

}

public override string GetDetails() =>

base.GetDetails() + " + Security Package";

public override double GetPrice() =>

base.GetPrice() + 1;

}

public class SportPackage : BikeAccessories

{

public SportPackage(IBike bike) : base(bike)

{

}

public override string GetDetails() =>

base.GetDetails() + " + Sport Package";

public override double GetPrice() =>

base.GetPrice() + 10;

}

public class BikeShop

{

public static void UpgradeBike()

{

var basicBike = new AluminiumBike();

BikeAccessories upgraded = new SportPackage(basicBike);

upgraded = new SecurityPackage(upgraded);

Console.WriteLine($"Bike: '{upgraded.GetDetails()}' Cost: {upgraded.GetPrice()}");

}

}

```

위 코드의 출력 결과는 다음과 같다.

```

Bike: 'Aluminium Bike + Sport Package + Security Package' Cost: 111

```

다음은 PHP 코드 예제이다.

```php

abstract class Component

{

protected $data;

protected $value;

abstract public function getData();

abstract public function getValue();

}

class ConcreteComponent extends Component

{

public function __construct()

{

$this->value = 1000;

$this->data = "Concrete Component:\t{$this->value}\n";

}

public function getData()

{

return $this->data;

}

public function getValue()

{

return $this->value;

}

}

abstract class Decorator extends Component

{

}

class ConcreteDecorator1 extends Decorator

{

public function __construct(Component $data)

{

$this->value = 500;

$this->data = $data;

}

public function getData()

{

return $this->data->getData() . "Concrete Decorator 1:\t{$this->value}\n";

}

public function getValue()

{

return $this->value + $this->data->getValue();

}

}

class ConcreteDecorator2 extends Decorator

{

public function __construct(Component $data)

{

$this->value = 500;

$this->data = $data;

}

public function getData()

{

return $this->data->getData() . "Concrete Decorator 2:\t{$this->value}\n";

}

public function getValue()

{

return $this->value + $this->data->getValue();

}

}

class Client

{

private $component;

public function __construct()

{

$this->component = new ConcreteComponent();

$this->component = $this->wrapComponent($this->component);

echo $this->component->getData();

echo "Client:\t\t\t";

echo $this->component->getValue();

}

private function wrapComponent(Component $component)

{

$component1 = new ConcreteDecorator1($component);

$component2 = new ConcreteDecorator2($component1);

return $component2;

}

}

$client = new Client();

// 결과: #quanton81

//Concrete Component: 1000

//Concrete Decorator 1: 500

//Concrete Decorator 2: 500

//Client: 2000

4. 1. Java 예제

다음은 윈도우/스크롤 시나리오를 사용한 자바 예제이다.[7]

```java

// Coffee 인터페이스는 데코레이터에 의해 구현된 커피의 기능을 정의합니다.

public interface Coffee {

public double getCost(); // 커피의 비용을 반환합니다.

public String getIngredients(); // 커피의 재료를 반환합니다.

}

// 추가 재료가 없는 단순한 커피의 확장입니다.

public class SimpleCoffee implements Coffee {

@Override

public double getCost() {

return 1;

}

@Override

public String getIngredients() {

return "Coffee";

}

}

```

다음 클래스들은 데코레이터 클래스 자체를 포함하여 모든 `Coffee` 클래스에 대한 데코레이터를 포함한다.

```java

// 추상 데코레이터 클래스 - Coffee 인터페이스를 구현한다는 점에 유의하십시오.

public abstract class CoffeeDecorator implements Coffee {

private final Coffee decoratedCoffee;

public CoffeeDecorator(Coffee c) {

this.decoratedCoffee = c;

}

@Override

public double getCost() { // 인터페이스의 메서드 구현

return decoratedCoffee.getCost();

}

@Override

public String getIngredients() {

return decoratedCoffee.getIngredients();

}

}

// Decorator WithMilk는 우유를 커피에 섞습니다.

// CoffeeDecorator를 확장합니다.

class WithMilk extends CoffeeDecorator {

public WithMilk(Coffee c) {

super(c);

}

@Override

public double getCost() { // 추상 상위 클래스에 정의된 메서드를 재정의합니다.

return super.getCost() + 0.5;

}

@Override

public String getIngredients() {

return super.getIngredients() + ", Milk";

}

}

// Decorator WithSprinkles는 스프링클을 커피 위에 섞습니다.

// CoffeeDecorator를 확장합니다.

class WithSprinkles extends CoffeeDecorator {

public WithSprinkles(Coffee c) {

super(c);

}

@Override

public double getCost() {

return super.getCost() + 0.2;

}

@Override

public String getIngredients() {

return super.getIngredients() + ", Sprinkles";

}

}

```

테스트 프로그램 및 출력 결과는 다음과 같다.

```java

public class Main {

public static void printInfo(Coffee c) {

System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());

}

public static void main(String[] args) {

Coffee c = new SimpleCoffee();

printInfo(c);

c = new WithMilk(c);

printInfo(c);

c = new WithSprinkles(c);

printInfo(c);

}

}

```

```

Cost: 1.0; Ingredients: Coffee

Cost: 1.5; Ingredients: Coffee, Milk

Cost: 1.7; Ingredients: Coffee, Milk, Sprinkles

```

다음은 자바를 이용한 또 다른 예시이다.

```java

/** 가격을 나타내는 인터페이스 */

interface Price{

int getValue();

}

/** 원가를 나타내는 클래스 */

class PrimePrice implements Price{

private int value;

PrimePrice(int value){

this.value = value;

}

public int getValue(){

return this.value;

}

}

/** 마진을 거치는 가격 */

abstract class MarginPrice implements Price{

protected Price originalPrice;

MarginPrice(Price price){

this.originalPrice = price;

}

}

/** 설정된 이익을 매입 가격에 더하는 Price */

class WholesalePrice extends MarginPrice{

private int advantage;

WholesalePrice(Price price, int advantage){

super(price);

this.advantage = advantage;

}

public int getValue(){

return this.originalPrice.getValue() + advantage;

}

}

/** 매입 가격의 2배 가격을 제시하는 Price */

class DoublePrice extends MarginPrice{

DoublePrice(Price price){

super(price);

}

public int getValue(){

return this.originalPrice.getValue() * 2;

}

}

public class DecoratorTest{

public static void main(String[] argv){

System.out.println(

new WholesalePrice(

new DoublePrice(

new WholesalePrice(

new DoublePrice(

new PrimePrice(120)

)

,80

)

)

,200

)

.getValue()

);

}

}

```

클래스 다이어그램과의 관계는 다음과 같다.

  • Component: `Price`
  • ConcreteComponent: `PrimePrice`
  • Decorator: `MarginPrice`
  • ConcreteDecorator: `WholesalePrice`, `DoublePrice`

4. 1. 1. Window 인터페이스

java

// Window 인터페이스

public interface Window {

void draw(); // Window를 그린다.

String getDescription(); // Window의 설명을 반환한다.

}

// 스크롤바가 없는 간단한 Window 구현

class SimpleWindow implements Window {

@Override

public void draw() {

// 윈도우를 그린다.

}

@Override

public String getDescription() {

return "simple window";

}

}

```

위 코드는 `Window` 인터페이스와 이를 구현하는 `SimpleWindow` 클래스를 보여준다. `Window` 인터페이스는 `draw()`와 `getDescription()` 메서드를 정의하고, `SimpleWindow`는 이 메서드들을 구현하여 기본적인 창을 표현한다. `SimpleWindow`의 `getDescription()`은 "simple window"를 반환한다.[7]

4. 1. 2. WindowDecorator

java

// Window 인터페이스 클래스

public interface Window {

void draw(); // Window를 그린다.

String getDescription(); // Window의 설명을 반환한다.

}

// 스크롤바가 없는 간단한 Window 구현

class SimpleWindow implements Window {

@Override

public void draw() {

// 윈도우를 그린다.

}

@Override

public String getDescription() {

return "simple window";

}

}

```

다음 클래스들은 데코레이터 클래스 자체를 포함하여 모든 `Window` 클래스에 대한 데코레이터를 포함한다.[7]

```java

// 추상 데코레이터 클래스 - Window를 구현한다는 점에 유의

abstract class WindowDecorator implements Window {

private final Window windowToBeDecorated; // 데코레이션될 Window

public WindowDecorator (Window windowToBeDecorated) {

this.windowToBeDecorated = windowToBeDecorated;

}

@Override

public void draw() {

windowToBeDecorated.draw(); // 위임

}

@Override

public String getDescription() {

return windowToBeDecorated.getDescription(); // 위임

}

}

// 수직 스크롤바 기능을 추가하는 첫 번째 구체적인 데코레이터

class VerticalScrollBarDecorator extends WindowDecorator {

public VerticalScrollBarDecorator (Window windowToBeDecorated) {

super(windowToBeDecorated);

}

@Override

public void draw() {

super.draw();

drawVerticalScrollBar();

}

private void drawVerticalScrollBar() {

// 수직 스크롤바를 그린다.

}

@Override

public String getDescription() {

return super.getDescription() + ", including vertical scrollbars";

}

}

// 수평 스크롤바 기능을 추가하는 두 번째 구체적인 데코레이터

class HorizontalScrollBarDecorator extends WindowDecorator {

public HorizontalScrollBarDecorator (Window windowToBeDecorated) {

super(windowToBeDecorated);

}

@Override

public void draw() {

super.draw();

drawHorizontalScrollBar();

}

private void drawHorizontalScrollBar() {

// 수평 스크롤바를 그린다.

}

@Override

public String getDescription() {

return super.getDescription() + ", including horizontal scrollbars";

}

}

```

다음은 완전히 데코레이션된 (즉, 수직 및 수평 스크롤바가 있는) `Window` 인스턴스를 생성하고 해당 설명을 출력하는 테스트 프로그램이다.[7]

```java

public class DecoratedWindowTest {

public static void main(String[] args) {

// 수평 및 수직 스크롤바가 있는 데코레이션된 Window를 생성한다.

Window decoratedWindow = new HorizontalScrollBarDecorator (

new VerticalScrollBarDecorator (new SimpleWindow()));

// Window의 설명을 출력한다.

System.out.println(decoratedWindow.getDescription());

}

}

```

이 프로그램의 출력은 "simple window, including vertical scrollbars, including horizontal scrollbars"이다. 두 데코레이터의 `getDescription()` 메서드가 먼저 데코레이션된 `Window`의 설명을 가져와 접미사로 이를 ''데코레이션''하는 방식을 확인하십시오.[7]

다음은 테스트 주도 개발을 위한 JUnit 테스트 클래스이다.

```java

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class WindowDecoratorTest {

@Test

public void testWindowDecoratorTest() {

Window decoratedWindow = new HorizontalScrollBarDecorator(new VerticalScrollBarDecorator(new SimpleWindow()));

// 설명에 실제로 수평 + 수직 스크롤바가 포함되어 있는지 확인한다.

assertEquals("simple window, including vertical scrollbars, including horizontal scrollbars", decoratedWindow.getDescription());

}

}

```

위의 UML 클래스 다이어그램에서, 추상 클래스 `Decorator`는 데코레이션된 객체(`Component`)에 대한 참조(`component`)를 유지하며 모든 요청을 해당 객체(`component.operation()`)로 전달한다. 이로 인해 `Decorator`는 `Component`의 클라이언트에게 투명하게(보이지 않게) 된다.[7]

하위 클래스(`Decorator1`, `Decorator2`)는 `Component`에 추가되어야 하는 추가적인 동작(`addBehavior()`)을 구현한다(요청을 전달하기 전/후).[7]

시퀀스 다이어그램은 실행 시간 상호 작용을 보여준다. `Client` 객체는 `Decorator1` 및 `Decorator2` 객체를 통해 작동하여 `Component1` 객체의 기능을 확장한다.[7]

`Client`는 `Decorator1`에 `operation()`을 호출하고, `Decorator1`은 `Decorator2`에 요청을 전달한다. `Decorator2`는 `Component1`에 요청을 전달한 후 `addBehavior()`를 수행하고 `Decorator1`로 반환하며, `Decorator1`은 `addBehavior()`를 수행하고 `Client`로 반환한다.[7]

4. 1. 3. DecoratedWindowTest

java

public class DecoratedWindowTest {

public static void main(String[] args) {

// 수평 및 수직 스크롤바가 있는 데코레이션된 Window를 생성

Window decoratedWindow = new HorizontalScrollBarDecorator (

new VerticalScrollBarDecorator (new SimpleWindow()));

// Window의 설명 출력

System.out.println(decoratedWindow.getDescription());

}

}

```

이 프로그램의 출력 결과는 "simple window, including vertical scrollbars, including horizontal scrollbars"이다. 이는 `HorizontalScrollBarDecorator`와 `VerticalScrollBarDecorator`의 `getDescription()` 메서드가 먼저 데코레이션된 `Window`의 설명을 가져온 후, 각각 "including horizontal scrollbars"와 "including vertical scrollbars"를 추가하여 최종 설명을 생성하기 때문이다.

4. 2. C++ 예제

cpp

#include

#include

// 음료 인터페이스

class Beverage {

public:

virtual void drink() = 0;

virtual ~Beverage() = default;

};

// 장식될 수 있는 음료

class Coffee : public Beverage {

public:

virtual void drink() override {

std::cout << "커피를 마시는 중";

}

};

class Soda : public Beverage {

public:

virtual void drink() override {

std::cout << "소다를 마시는 중";

}

};

// 음료 데코레이터

class BeverageDecorator : public Beverage {

public:

BeverageDecorator() = delete;

BeverageDecorator(std::unique_ptr component_)

: component(std::move(component_))

{

}

virtual void drink() = 0;

protected:

void callComponentDrink() {

if (component) {

component->drink();

}

}

private:

std::unique_ptr component;

};

// 우유 데코레이터

class Milk : public BeverageDecorator {

public:

Milk(std::unique_ptr component_, float percentage_)

: BeverageDecorator(std::move(component_))

, percentage(percentage_)

{

}

virtual void drink() override {

callComponentDrink();

std::cout << ", 우유는 " << percentage << "%";

}

private:

float percentage;

};

// 얼음 데코레이터

class IceCubes : public BeverageDecorator {

public:

IceCubes(std::unique_ptr component_, int count_)

: BeverageDecorator(std::move(component_))

, count(count_)

{

}

virtual void drink() override {

callComponentDrink();

std::cout << ", 얼음 " << count << " 개";

}

private:

int count;

};

// 설탕 데코레이터

class Sugar : public BeverageDecorator {

public:

Sugar(std::unique_ptr component_, int spoons_)

: BeverageDecorator(std::move(component_))

, spoons(spoons_)

{

}

virtual void drink() override {

callComponentDrink();

std::cout << ", 설탕 " << spoons << " 스푼";

}

private:

int spoons = 1;

};

int main() {

// 소다에 얼음 3개, 설탕 1스푼 추가

std::unique_ptr soda = std::make_unique();

soda = std::make_unique(std::move(soda), 3);

soda = std::make_unique(std::move(soda), 1);

soda->drink();

std::cout << std::endl;

// 커피에 얼음 16개, 우유 3%, 설탕 2스푼 추가

std::unique_ptr coffee = std::make_unique();

coffee = std::make_unique(std::move(coffee), 16);

coffee = std::make_unique(std::move(coffee), 3.);

coffee = std::make_unique(std::move(coffee), 2);

coffee->drink();

std::cout << std::endl;

return 0;

}

```

프로그램 출력은 다음과 같다.

```

커피를 마시는 중, 얼음 3 개, 설탕 1 스푼

커피를 마시는 중, 얼음 16 개, 우유는 3%, 설탕 2 스푼

4. 3. 다른 프로그래밍 언어 예제

C++, Java, PHP, Python, Ruby, C# 등 다양한 프로그래밍 언어에서 데코레이터 패턴을 구현할 수 있다.

  • C++:
  • `Shape` 인터페이스와 `Circle`, `ColoredShape` 클래스를 통해 도형에 색상을 추가하는 예제를 들 수 있다.
  • `WebPage` 인터페이스와 `BasicWebPage`, `WebPageDecorator`, `AuthenticatedWebPage`, `AuthorizedWebPage` 클래스를 통해 웹 페이지에 인증 및 인가 기능을 추가하는 예시를 구현할 수 있다.
  • 템플릿을 사용하여 정적 데코레이터 구현이 가능하다. `ColoredShape` 템플릿 클래스는 `Circle` 클래스에 색상 속성을 추가한다.

  • Java:
  • `Coffee` 인터페이스와 `SimpleCoffee`, `CoffeeDecorator`, `WithMilk`, `WithSprinkles` 클래스를 통해 커피에 우유, 스프링클 등의 재료를 추가하는 예제를 통해 데코레이터 패턴을 설명한다.

  • PHP:
  • 추상 클래스 `Component`와 `ConcreteComponent`, `Decorator`, `ConcreteDecorator1`, `ConcreteDecorator2` 클래스를 통해 구성 요소에 기능을 추가하는 예시를 보여준다.

  • Python:
  • `RandomSquare`, `DataSquare`, `CacheDecorator`, `MaxDecorator`, `MinDecorator`, `VisibilityDecorator` 클래스를 통해 10x10 그리드 세상에서 값을 가져오고, 최대/최소값을 제한하며, 가시성을 제공하는 기능을 추가하는 예제를 제시한다.
  • 파이썬의 데코레이터 문법과는 다른 개념임을 명시한다.[8]

  • Ruby:
  • `Coffee`, `SimpleCoffee`, `CoffeeDecorator`, `WithMilk`, `WithSprinkles` 클래스를 통해 커피에 우유와 스프링클을 추가하는 예제를 보여준다.
  • `SimpleDelegator`를 사용하여 데코레이터 패턴을 구현할 수 있다.

  • C#:
  • `IBike` 인터페이스와, `AluminiumBike`, `CarbonBike`, 추상클래스인 `BikeAccessories`, `SecurityPackage`, `SportPackage` 클래스를 통해 자전거에 보안 및 스포츠 패키지를 추가하는 예시를 확인할 수 있다.

5. 장점 및 단점

데코레이터 패턴은 실행 시간에 객체의 기능을 추가하거나 제거할 수 있어 유연하며, 기존 코드를 수정하지 않고도 기능을 확장할 수 있어 확장성이 뛰어나다. 각 데코레이터는 하나의 특정 기능에 집중하므로 단일 책임 원칙(SRP)을 따르며, 기존 코드를 변경하지 않고 기능을 확장할 수 있어 개방-폐쇄 원칙(OCP)을 만족한다.[5]

합성 방식을 지원하며, 실행 시간에 인터페이스의 동작을 추가하거나 변경할 수 있게 해준다. 객체를 다층적이고 임의의 조합으로 감싸는 것이 가능하며, 이는 서브클래스를 사용하는 것보다 메모리 효율적이다.[6]

그러나 데코레이터 패턴에는 다음과 같은 단점도 존재한다.


  • 복잡성: 데코레이터 클래스가 많아질수록 코드가 복잡해질 수 있다.
  • 객체 관리: 데코레이터로 감싸진 객체를 관리하기 어려울 수 있다.
  • 초기 설정: 데코레이터를 조합하여 객체를 생성하는 초기 설정이 복잡할 수 있다.

5. 1. 장점

데코레이터 패턴은 런타임에 객체의 기능을 동적으로 추가하거나 제거할 수 있어 유연성을 제공한다. 새로운 기능을 추가하기 위해 기존 코드를 수정할 필요가 없어 확장성이 뛰어나다. 각 데코레이터는 하나의 특정 기능에 집중하므로 단일 책임 원칙(SRP)을 따르며, 기존 코드를 변경하지 않고 기능을 확장할 수 있어 개방-폐쇄 원칙(OCP)을 만족한다.[5]

이는 합성 방식을 지원하며, 런타임에 인터페이스의 동작을 추가하거나 변경할 수 있게 해준다. 객체를 다층적이고 임의의 조합으로 래핑하는 것이 가능하며, 이는 서브클래스를 사용하는 것보다 메모리 효율적이다.[6]

5. 2. 단점

데코레이터 패턴은 프로그램 실행 시에 기능을 추가할 수 있다는 장점이 있지만, 다음과 같은 단점도 존재한다.

  • 복잡성: 데코레이터 클래스가 많아질수록 코드가 복잡해질 수 있다.
  • 객체 관리: 데코레이터로 감싸진 객체를 관리하기 어려울 수 있다.
  • 초기 설정: 데코레이터를 조합하여 객체를 생성하는 초기 설정이 복잡할 수 있다.

6. 활용 사례

데코레이터 패턴은 다양한 분야에서 활용된다.


  • GUI 툴킷: 버튼, 텍스트 필드 등 UI 요소에 스크롤, 테두리, 그림자 등의 기능을 추가하는 데 사용될 수 있다. 예를 들어, 텍스트 편집 애플리케이션에서 텍스트를 강조 표시하는 버튼은 명령 패턴과 함께 데코레이터 패턴을 활용하여 구현될 수 있다.[4]
  • I/O 스트림: Java와 .NET Framework의 I/O 스트림은 데코레이터 패턴을 사용하여 파일 입출력, 네트워크 통신 등에 버퍼링, 암호화 등의 기능을 추가한다.[5]
  • 웹 개발: 웹 페이지에 인증, 권한 부여, 로깅 등의 기능을 추가하는 데 사용될 수 있다.
  • 게임 개발: 게임 캐릭터에 아이템, 스킬 등의 기능을 추가하는 데 사용될 수 있다.
  • API 개선: 퍼사드 패턴으로 단순화된 인터페이스에 기능을 추가할 수 있다. 예를 들어, 여러 언어 사전을 통합하는 다국어 사전 인터페이스에 언어 간 단어 번역 기능을 데코레이터 패턴으로 추가할 수 있다.


데코레이터 패턴은 플라이급 패턴과 함께 사용되어 메모리 사용량을 줄일 수도 있다. 예를 들어, iOS의 UITableView는 재사용 가능한 셀을 데코레이터로 활용하여 플라이급 패턴을 구현한다.[4]

7. 다른 패턴과의 관계

데코레이터는 기능 확장에 대해 합성 방식을 지원하며, 하향식의 계층적 접근 방식은 아니다. 데코레이터를 사용하면 런타임에 인터페이스의 동작을 추가하거나 변경할 수 있다. 객체를 다층적이고 임의의 조합으로 래핑하는 데 사용할 수 있다. 서브클래스를 사용하여 동일한 작업을 수행하는 것은 복잡한 다중 상속 네트워크를 구현하는 것을 의미하며, 이는 메모리 비효율적이며 특정 시점에서는 확장할 수 없다. 마찬가지로 속성을 사용하여 동일한 기능을 구현하려는 시도는 객체의 각 인스턴스를 불필요한 속성으로 부풀린다.[5]

위와 같은 이유로 데코레이터는 서브클래싱의 메모리 효율적인 대안으로 간주되는 경우가 많다. 데코레이터는 서브클래스화할 수 없고, 런타임에 특성이 변경되어야 하거나 일반적으로 필요한 일부 기능이 부족한 객체를 특수화하는 데에도 사용할 수 있다.[5]

7. 1. 어댑터 패턴 (Adapter Pattern)

어댑터은 클라이언트가 기대하는 것에 맞춰 한 인터페이스를 다른 인터페이스로 변환하는 반면,[6] 데코레이터는 원래 코드를 래핑하여 인터페이스에 기능을 동적으로 추가한다.[6] 즉, 데코레이터 패턴은 인터페이스를 변경하지 않고 기능을 추가한다.

다음 표는 각 패턴의 의도를 나타낸다.

패턴의도
어댑터클라이언트가 기대하는 것에 맞춰 한 인터페이스를 다른 인터페이스로 변환
데코레이터원래 코드를 래핑하여 인터페이스에 책임을 동적으로 추가
퍼사드단순화된 인터페이스 제공


7. 2. 퍼사드 패턴 (Facade Pattern)

퍼사드은 복잡한 시스템에 대한 단순한 인터페이스를 제공하지만, 데코레이터 패턴은 기존 인터페이스에 기능을 추가한다.[6]

패턴의도
어댑터클라이언트가 기대하는 것에 맞춰 한 인터페이스를 다른 인터페이스로 변환
데코레이터원래 코드를 래핑하여 인터페이스에 책임을 동적으로 추가
퍼사드단순화된 인터페이스 제공


7. 3. 컴포지트 패턴 (Composite Pattern)

Composite Pattern영어 (컴포지트 패턴)은 객체들을 트리 구조로 구성하여 부분-전체 계층을 표현하는 반면, 데코레이터 패턴은 객체에 기능을 동적으로 추가한다.[5] 데코레이터는 기능 확장에 대해 합성 방식을 지원하며, 하향식의 계층적 접근 방식은 아니다. 데코레이터를 사용하면 런타임에 인터페이스의 동작을 추가하거나 변경할 수 있다. 객체를 다층적이고 임의의 조합으로 래핑하는 데 사용할 수 있다. 서브클래스를 사용하여 동일한 작업을 수행하는 것은 복잡한 다중 상속 네트워크를 구현하는 것을 의미하며, 이는 메모리 비효율적이며 특정 시점에서는 확장할 수 없다. 마찬가지로 속성을 사용하여 동일한 기능을 구현하려는 시도는 객체의 각 인스턴스를 불필요한 속성으로 부풀린다.[5]

데코레이터는 서브클래스화할 수 없고, 런타임에 특성이 변경되어야 하거나 일반적으로 필요한 일부 기능이 부족한 객체를 특수화하는 데에도 사용할 수 있다.

8. 한국적 상황과 데코레이터 패턴

빠르게 변화하는 한국 IT 산업 환경에서 데코레이터 패턴의 유연성과 확장성은 큰 장점이 된다. 특히 다양한 기능을 동적으로 추가해야 하는 전자정부 서비스 개발에 유용하게 활용될 수 있다. 한국은 온라인 게임 강국이며, 게임 캐릭터나 아이템에 다양한 기능을 추가해야 하는 경우가 많으므로 데코레이터 패턴이 적합하다. 또한 빠르게 변화하는 시장 요구에 맞춰 서비스를 개발하고 수정해야 하는 스타트업에게 데코레이터 패턴은 유용한 도구가 될 수 있다.[1]

9. 결론

데코레이터 패턴은 프로그램 실행 중에 기능을 추가하기 위해, 기존 객체를 새로운 `Decorator` 객체로 감싸는 방식이다. 이는 컴파일 시점에 기능을 확장하는 클래스 상속과는 다르다. `Decorator`의 생성자는 보통 감쌀 대상인 `Component` 객체를 인수로 받아, 생성자 내부에서 해당 객체를 멤버로 설정한다.

참조

[1] 서적 Design Patterns https://archive.org/[...] Addison-Wesley Publishing Co, Inc.
[2] 웹사이트 How to Implement a Decorator Pattern http://sam-burns.co.[...]
[3] 웹사이트 The Decorator Pattern, Why We Stopped Using It, and the Alternative https://betterprogra[...] 2022-03-08
[4] 서적 Design Patterns: Elements of Reusable Object-Oriented Software https://archive.org/[...] Addison Wesley
[5] 웹사이트 The Decorator design pattern - Problem, Solution, and Applicability http://w3sdesign.com[...] 2017-08-12
[6] 서적 Head First Design Patterns https://www.goodread[...] O'Reilly 2012-07-02
[7] 웹사이트 The Decorator design pattern - Structure and Collaboration http://w3sdesign.com[...] 2017-08-12
[8] 웹사이트 DecoratorPattern - Python Wiki https://wiki.python.[...]



본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.

문의하기 : help@durumis.com