데코레이터란 장식하다 꾸미다라는 뜻이 가지고 있다.
그럼 무엇을 꾸민다는 것일까?
우선 데코레이터 패턴을 알아보기 앞서 헤드퍼스트 디자인패턴 책에서 나온것 처럼 커피로 예제로 들것이다.
우선 음료가 있다. 이 음료는 가격 이랑 설명을 알 수 있다. 그리고 음료에 첨가할 첨가물 이 있다 이 첨가물도 가격이랑 설명을 알 수 있다.
우선 음료를 구현하는 것은 espresso 로 할 것이고 첨가물은 mocha 로 할 것이다.

다음과 같이 코드로 나타내면
public interface Beverage {
String getDescription();
double cost();
}
public interface CondimentDecorator {
String getDescription();
double cost();
}
public class Expresso implements Beverage{
String description = "에스프레소";
double cost = 1.99;
public CondimentDecorator condimentDecorator;
public Expresso(CondimentDecorator condimentDecorator){
this.condimentDecorator = condimentDecorator;
}
@Override
public String getDescription() {
return this.description + condimentDecorator.getDescription();
}
@Override
public double cost() {
return this.cost + condimentDecorator.cost();
}
}
public class Mocha implements CondimentDecorator{
String description = "에스프레소";
double cost = 0.20;
@Override
public String getDescription() {
return description;
}
@Override
public double cost() {
return cost;
}
}
실행
public class StarbuzzCoffe {
public static void main(String[] args){
CondimentDecorator mocha = new Mocha();
Beverage expresso =new Expresso(mocha);
System.out.println(expresso.getDescription());
System.out.println(expresso.cost());
}
}
다음과 같이 코드로 나타내면 나름 전략 패턴으로 캡슐화도 잘 시켜주었고 의존성 주입으로 통해 ioc 도 꽤 만족 시키는 코드인 것 같다.

다음과 같이 결과도 만족스럽게 잘 뜬다.
허나 여기서 하나 생각해야 할 것이 있다.
만약 옵션이 더욱 생겨 바닐라 샷 , 헤이즐럿 샷이 생겼다.
그럼 옵션을 모카, 바닐라샷, 헤이즐럿 샷으로 할 수 도 있고 (모카, 바닐라샷), (모카, 헤이즐럿샷), (바닐라샷, 헤이즐럿 샷) 일수도 있고 (모카, 바닐라샷, 헤이즐럿 샷) 일 수도 있다. 즉 옵션이 추가 될때 마다 구현 해야할 클래스가 조합으로 늘어난다는 문제가 있다.
그럼 이를 어떻게 해결 할 수 있을 것인가?
이것이 바로 데코레이터 패턴이다.
데코레이터 패턴 말그대로 꾸며주는 것이다.
즉 객체에 추가 요소를 동적으로 더할 수 있으며 훨씬 유연하게 기능을 확장할 수 있는 패턴이다.
여기서 동적이란 컴파일 시점 이 아닌 런타임 시점에서 기능을 추가할 수 있다는 뜻이며 즉 컴파일 시점인 클래스를 제작하는 것이 아닌 런타임에서 기능을 추가하는 것이다.
그럼 어떻게 이것이 가능 한 것일까?

책 그림처럼 바로 객체를 계속 감싸는 것이다.
코드로 보면
public abstract class Beverage {
String description = "제목 없음";
public String getDescription(){
return description;
}
public abstract double cost();
}
public abstract class CondimentDecorator extends Beverage{
Beverage beverage;
public abstract String getDescription();
@Override
public double cost() {
return 0;
}
}
여기서 주목해야 할 점은 첨가물 즉 CondimentDecorator 안에 Beverage 자기 자신이 있는 것이다.
즉 target 을 불러와서 기능을 추가해 주면 형이 변하지도 않고 기능을 추가 할 수있다.
public class Espresso extends Beverage{
public Espresso(){
description = "에스프로소";
}
@Override
public double cost() {
return 1.99;
}
}
public class Mocha extends CondimentDecorator{
public Mocha(Beverage beverage){
this.beverage = beverage;
}
public String getDescription(){
return beverage.getDescription() + "모카";
}
@Override
public double cost() {
return beverage.cost() + .20;
}
}
public class StarbuzzCoffe {
public static void main(String args[]){
Beverage beverage = new Espresso();
beverage = new Mocha(beverage);
System.out.println(beverage.getDescription());
System.out.println(beverage.cost());
}
}
beverage = new Mocha(beverage);
즉 다음과 같이 객체에 추가하고 싶은 기능을 같은 타입으로 확장 시킨 후 해당 클래스에서 기능을 추가해 주면 된다
이러면 만약 바닐라샷, 헤이즐 럿 샷이 생겼다 해도
바닐라샷(모카(에스프레소)), 헤이즐럿 샷(모카(에스프레소)) 처럼 컴파일시 동적으로 기능을 추가 할 수 있는 것이다.
참고로 이 형태를 프록시라 부르기로 했다.
여기서 추가적인 것은 다이나믹 프록시를 보면 된다.