기록하자..
Decorator 패턴 본문
장식 패턴?
객체에 동적으로 새로운 행위를 추가할 수 있도록 해주는 패턴
Head First Design Pattern에서..
Beverage라는 클래스와 이를 상속받는 여러 음료 클래스들을 예시로 들고 있다.

커피를 주문할 때 모카를 추가하거나 휘핑크림 등을 추가하기도 한다. 이럴 때 추가 토핑들마다 가격이 달라지기 때문에 주문 시스템에서도 이를 고려해야 한다.
그렇다면 모든 조합을 Beverage를 상속하는 클래스들로 만드는 것이 바람직할까?
조합이 너무 많아질 경우에는 클래스가 너무 많아져 클래스 폭발 문제가 발생할 수 있다.
그렇다면 이는 어떻게 해결할 수 있을까?
해결책?
토핑에 대한 인스턴스 변수와 이를 알아내거나 설정하기위한 getter/setter 메서드를 사용한다.
하지만 이는 토핑의 가격이 변동되면 기존 코드를 수정해야하고 변하는 부분을 추상화하지 못했다.
또한 새로운 토핑들(ex. 카라멜)이 생길 때마다 클래스의 멤버변수와 메서드를 추가해야한다. 이는 OCP원칙에 위배된다.
그리고 상속을 통해 행위를 상속받고 있는데, 이 상속되는 행위는 컴파일 시간에 고정되며, 모든 자식 클래스는 동일한 행위를 상속받아야 한다.
Decorator Pattern
그렇다면 음료를 어떻게 "장식"할 수 있을까?
만약 고객이 원하는 것이 DarkRoast Mocha Whip이면 DarkRoast에서 시작해서 장식하는 것이다.
- 1. DarkRoast 객체를 가져온다.
- 2. Mocha 객체로 장식한다.
- 3. Whip 객체로 장식한다.
- 4. cost() 메서드를 호출한다. 이때 첨가물의 가격계산은 해당 객체들에게 위임한다.
여기서 Decorator Patter에 대해 좀 더 알아가 보자.
장식자의 수퍼클래스는 자신이 장식하고 있는 객체의 수퍼클래스와 같다.
- 이는 장식된 객체를 원래 객체 대신에 사용 가능하다는 것을 의미한다.
- 이를 위해 기존 객체를 상속받아 정의하지만 행위의 추가는 상속을 통해 이루어지는 것은 아니다. (has-a 활용)
- 상속을 사용하지만 is-a 관계는 아니다.
- 위임(has-a)을 통한 문제 해결 ( super.cost() X → beverage.cost() )
한 객체를 여러 개의 장식자로 장식할 수 있다.
동적으로 실행시간에 객체를 장식할 수 있다. → 객체에 동적으로 새로운 책임(행위, 상태)을 추가할 수 있다.
조금 더 이해하기 쉽도록 클래스 다이어그램을 살펴보자.

Component는 우리가 사용할 객체의 기본적인 틀이다. 장식할 구체적인 타입(장식대상)은 ConcreteComponent로 되어 있고, 장식 추상 클래스 Decorator에서 자신이 장식할 구성요소와 같은 인터페이스 또는 추상클래스를 구현한다.
그리고 장식자들인 ConcreteDecorator에서 구성요소인 Component를 has-a로 유지하고 있다.
그러면 이제 코드를 디자인 해보자
public abstract class Beverage {
private String description;
public Beverage() {
description = "No title";
}
public Beverage(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public abstract double cost();
}
public abstract class CondimentDecorator extends Beverage {
public abstract class String getDescription();
}
public class Espresso extends Beverage {
public Espresso() {
super("Espresso");
}
@Override
public double cost() {
return 1.99;
}
}
public class Mocha extends CondimentDecorator {
Beverage beverage;
public Mocha (Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", mocha";
}
@Override
public double cost() {
return 200 + beverage.cost();
}
}
문제점
- 특정 decoratee에 적용해야 하는 기능의 추가가 decorator 때문에 힘들 수 있다.
- ex. Espresso가 특별할인을 할 경우 decorator를 사용하면 제대로 동작하지 않을 수 있다.
if(beverage instanceof Espresso) return beverage.cost() * 0.75;
구상 구성요소를 바탕으로 돌아가는 코드를 만들어야 한다면 디자인 자체 및 데코레이터 패턴을 사용하는 것을 다시 한 번 생각해 볼 필요가 있다.
- 생성할 때 오류가 많을 수 있다. -> 생성 패턴을 통해 극복 가능
- 여러 겹으로 장식된 경우, 가장 바깥쪽 decorator가 안쪽 decorator를 알 수 있을까?
- ex. 모카로 두 번 장식하면 모카, 모카 대신 더블 모카로 출력
위의 예는 가능은 하지만, 캡슐화 측면에서 좋지않다.
데코레이터는 그 데코레이터가 감싸고 있는 행동을 추가하기 위한 용도로 만들어진 것이다.
위와 같이 여러 단계의 데코레이터를 파고 들어 작업해야 한다면, 원래 데코레이터 패턴이 만들어진 의도와 어긋난 것이다.
데코레이터 패턴 적용 예 : 자바 I/O
자바의 java.io 패키지의 많은 부분이 데코레이터 패턴을 바탕으로 만들어졌다. 다음 그림과 같다.

Reader reader = new FileReader("data.txt");
reader = new BufferedReader(reader);

BufferedInputStream과 LineNumberInputStream 모두 FilterInputStream을 확장한 클래스이다.
위의 코드 디자인과 크게 다르지 않다.
자바 I/O를 보면 데코레이터의 단점도 알 수 있다. 이 패턴을 이용해 디자인을 하다 보면 잡다한 클래스들이 너무 많아진다는 것이다.
'디자인패턴공부' 카테고리의 다른 글
| 명령 패턴(Command Pattern) (0) | 2021.11.28 |
|---|---|
| 생성 패턴(Factory Pattern) (0) | 2021.11.28 |
| 관찰자 패턴(Observer Pattern) (0) | 2021.10.19 |
| 전략 패턴(Strategy Pattern) : Head First Design Pattern (0) | 2021.10.19 |
| SOLID (2) | 2021.10.15 |
