객체에 추가적인 요건을 동적으로 첨가한다. 테코레이터는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있는 방법을 제공한다.
우선 다음과 같은 시나리오를 가정한다.
스타벅스사는 사업이 잘되면서 이와 함께 판매하는 커피의 종류도 엄청 많아 지게 되었다. 이를 위해서 커피를 일반적인 Beverage라는 최상위 모델과 Beverage로 부터 확장된 각 음료를 모델링 하게되었다.하지만 확장되어지는 모델(음료종류)이 너무 많아지고 공통적인 내용을 상위(Beverage)클래스로 옮긴다고 하더라도 일부 서브클래스에는 적합하지 않는 기능을 베이스클래스에 추가되는 문제점이 나타나기 시작하였다.
위의 시나리오를 자세히 보면 음료마다 추가되는 재료에 따라 서브클래스의 특징이 달라지고 그에따라 음료 가격을 책정하는 방식도 달라지는 것을 알수 있다.
디자인 원칙 : 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다.(OCP: Open-Closed Principle)
Solution:
1. 특정 커피를 가져온다.
2. 그 커피에 Mocha 첨가 재료로 장식한다.
3. 그 커피에 Whip 첨가 재료로 장식한다.
4. cost()메소드를 호출한다. 이때 첨가물의 가격을 계산하는 일은 해당 첨가물 객체에게 위임한다.
http://stevenjsmin.tistory.com/trackback/97
위의 다이어그램은 데코레이터 패턴의 클래스 구조도이다. 각 데코레이터 안에는Component객체(구성요소)에 대한 레퍼런스가 들어있어서 다른 데코레이터(구성요소)를 감쌀 수가 있다.
다음은 위와 같은 디자인으로 설계된 스타벅스의 커피 모델링이다.
다음 코드는 최상위 구성요소(Component)에대한 추상코드이다.
package headfirst.decorator.starbuzz;
public abstract class Beverage { String description = "Unknown Beverage";
public String getDescription() { return description; }
public abstract double cost(); }
|
다음 코드는추상 첨가물(Decorator)코드와 실제 구성요소(Component 또는 Espresso, DarkRooast…)코드이다. 두가지 종류의 모델 모두 최상위 구성요소인 Beverage를 확장한다.
(추상)데코레이터 - 나중에 데코레이터들이 상속하게될 모델
package headfirst.decorator.starbuzz;
public abstract class CondimentDecorator extends Beverage { public abstract String getDescription(); }
|
구성요소
package headfirst.decorator.starbuzz;
public class Espresso extends Beverage {
public Espresso() { description = "Espresso"; }
public double cost() { return 1.99; } }
|
package headfirst.decorator.starbuzz;
public class DarkRoast extends Beverage { public DarkRoast() { description = "Dark Roast Coffee"; }
public double cost() { return .99; } }
|
package headfirst.decorator.starbuzz;
public class HouseBlend extends Beverage { public HouseBlend() { description = "House Blend Coffee"; }
public double cost() { return .89; } }
|
다음 코드는 데코레이터들이다. 이들 데코레이터 들은모두 추상 최상위 구성요소를 확장한 CondimentDecorator를 확장한다.
package headfirst.decorator.starbuzz;
public class Mocha extends CondimentDecorator { Beverage beverage;
public Mocha(Beverage beverage) { this.beverage = beverage; }
public String getDescription() { return beverage.getDescription() + ", Mocha"; }
public double cost() { return .20 + beverage.cost(); } }
package headfirst.decorator.starbuzz;
public class Whip extends CondimentDecorator { Beverage beverage;
public Whip(Beverage beverage) { this.beverage = beverage; }
public String getDescription() { return beverage.getDescription() + ", Whip"; }
public double cost() { return .10 + beverage.cost(); } }
package headfirst.decorator.starbuzz;
public class Soy extends CondimentDecorator { Beverage beverage;
public Soy(Beverage beverage) { this.beverage = beverage; }
public String getDescription() { return beverage.getDescription() + ", Soy"; }
public double cost() { return .15 + beverage.cost(); } }
|
다음코드는 스타벅스에서 어느 특정커피에대해서 데코레이션을 어떻게하고 그것에대한 가격 계산을 어떻게 하는지 보여주는 코드이다.
package headfirst.decorator.starbuzz; public class StarbuzzCoffee { public static void main(String args[]) { Beverage beverage = new Espresso(); System.out.println(beverage.getDescription() + " $" + beverage.cost()); Beverage beverage2 = new DarkRoast(); beverage2 = new Mocha(beverage2); beverage2 = new Mocha(beverage2); beverage2 = new Whip(beverage2); System.out.println(beverage2.getDescription() + " $" + beverage2.cost()); Beverage beverage3 = new HouseBlend(); beverage3 = new Soy(beverage3); beverage3 = new Mocha(beverage3); beverage3 = new Whip(beverage3); System.out.println(beverage3.getDescription() + " $" + beverage3.cost()); } }
실행결과 :
Espresso $1.99
Dark Roast Coffee, Mocha, Mocha, Whip $1.49
House Blend Coffee, Soy, Mocha, Whip $1.34
JDK에서의 데코레이터가 적용된 I/O
'Java Design Pattern' 카테고리의 다른 글
Template Method 패턴 (0) | 2013.02.20 |
---|---|
Factory 패턴 (0) | 2013.02.20 |
Observer 패턴 (0) | 2013.02.20 |
Strategy 패턴 - 알고리즘을 사용하는 클라이언트와 독립적으로 구현 (0) | 2013.02.20 |
Facade 패턴 (0) | 2013.02.19 |