![]() - Beverage는 음료를 나타내는 추상 클래스이며, 커피샵에서 판매되는 모든 음료는 이 클래스의 서브클래스가 된다. - 추상메소드인 cost()메소드를 새로 정의하여 가격을 구현한다. - 처음에는 하우스블렌드, 다크로스트, 디카페인, 에스프레소 네 가지만 판매 하게 된다.. |
![]() - 우와 클래스 개수가 말 그대로 폭발적으로 늘어나는 군.. - 정말 이대로 운영한다면 클래스 관리하는게 만만치가 않겠네. - 우유 가격이 인상된다면?, 카라멜 토핑을 새로 추가한다면? 생각만 해도 끔찍하네.. |
![]() |
|
사부와 제자
| ① DarkRoast 객체에서 시작 합니다. | ② 손님이 Mocha를 주문했으니 Mocha객체를 만들고 그 객체로 DarkRoast를 감쌉니다. | ③ 손님이 휘핑크림도 같이 주문했기 때문에 Whip 데코레이터를 만들고 그 ?체로 Mocha를 감쌉니다. |
![]() | ![]() | ![]() |
|
|
|
| ④ 이제 가격을 계산해 볼까요? 가장 바깥쪽에 있는 데코레이터 Whip의 cost()를 호출하면 된다. - 가장 바깥쪽에 있는 데코레이터 Whip의 cost()를 호출한다. - Whip에서는 Mocha의 cost() 메소드를 호출한다. - Mocha에서는 다시 DarkRoast의 cost()를 호출한다. - DarkRoast에서는 가격과 이름을 반환한다. - Mocha에서는 DarkRoast의 리턴값과 모카값을 더해 반환한다. - Whip에서는 Mocha에서 받은 가격에 Whip가격을더해 최종 가격을 반환한다. | ![]() |
![]() | ![]() - Beverage는 가장 기본이 되는 Component 추상 클래스로 볼 수 있다. - 커피 종류마다 Beverage에 대한 구상 클래스를 하나씩 만든다. (HouseBlend, DarkRoast, Expresso, Decaf) (Beverage클래스를 상속 받아 새로운 행동을 동적으로 추가하게 된다.) - 각각의 첨가물을 나타내는 데코레이터를 추가합니다. cost() 뿐만 아니라 getDescription() 도 구현해야 한다. (Mocha, Milk, Soy, Whip) - 각 데코레이터 안에는 Beverage 클래스가 들어있다. 즉, 데코레이터에는 구성요소에 대한 레퍼런스가 들어있는 인스턴스 변수가 있지요. |
| 사무실에서 들은 이야기.. |
|---|
|
| {code:title=Beverage.java | borderStyle=solid} public abstract class Beverage { |
protected String description = "제목없음";
public abstract double cost();
public String getDescription() {
return description;
}}
|{code:title=CondimentDecorator.java|borderStyle=solid}
public abstract class CondimentDecorator extends Beverage {
//모든 첨가물 데코레이터에서 getDescription() 메소드를 새로 구현하도록 만들 계획임
public abstract String getDescription();
}
|
| {code:title=Espresso.java | borderStyle=solid} //에스프레소 커피 public class Espresso extends Beverage { |
public Espresso(){
//Beverage로부터 상속받음
description = "에스프레소 커피";
}
@Override
public double cost() {
return 1.99;
}
}
|{code:title=Mocha.java|borderStyle=solid}
//Mocha는 데코레이터기 때문에 CondimentDecorator를 확장 합니다.
public class Mocha extends CondimentDecorator {
//감싸고자 하는 음료(하우스블렌드,다크로스트,디카페인,에스프레소)를 저장하는 인스턴스.
Beverage beverage;
//생성자를 이용해서 감싸고자 하는 음료 객체를 전달한다.
public Mocha(Beverage beverage){
this.beverage = beverage;
}
@Override
public String getDescription() {
//음료 명에 첨가물명을 추가한다.
return beverage.getDescription() + ", 모카";
}
//CondimentDecorator는 Beverage를 확장 하죠
@Override
public double cost() {
//음료 가격에 모카 가격을 추가한다.
return .20 + beverage.cost();
}
}
|
| {code:title=StarbuzzCoffee.java | borderStyle=solid} public class StarbuzzCoffee { public static void main(String[] args) { |
//에스프레소 커피
Beverage espresso = new Espresso();
System.out.println(espresso.getDescription()+
" : $"+espresso.cost());
//다크로스트 커피 + 모카+ 모카 + 휘핑크림
Beverage darkRoast = new DarkRoast(); //다크로스트 커피
darkRoast = new Mocha(darkRoast); //모카 추가
darkRoast = new Mocha(darkRoast); //모카 한번 더 추가
darkRoast = new Whip(darkRoast); //휘핑크림 추가
System.out.println(darkRoast.getDescription()+
" : $"+darkRoast.cost());
//하우스 블렌드 커피, 두유, 모카, 휘핑크림
Beverage houseBlend = new HouseBlend(); //하우스 블렌드 커피
houseBlend = new Soy(houseBlend); //두유 추가
houseBlend = new Mocha(houseBlend); //모카 추가
houseBlend = new Whip(houseBlend); //휘핑크림 추가
System.out.println(houseBlend.getDescription()+
" : $"+houseBlend.cost());
}
}
|에스프레소 커피 : $1.99
다크 로스트 커피, 모카, 모카, 휘핑크림 : $1.49
하우스 블렌드 커피, 두유, 모카, 휘핑크림 : $1.34|
h2. 2. 데코레이터 패턴(Decorator Pattern) 정의
h3. 2.1 데코레이터 패턴(Decorator Pattern) ?
* 데코레이터 패턴에서는 객체의 추가적인 요건을 동적으로 추가한다.
* 데코레이터는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있는 방법을 제공한다.
h3. 2.2 데코레이터의 단점
* 데코레이터 패턴을 이용해 디자인을 하다 보면 잡다한 클래스들이 많아 질 수 있다.
* 겹겹이 애워싼 객체의 정체를 알기가 힘들다.
h2. 3. 데코레이터가 적용된 예 : 자바 I/O
* java.io 패키지에는 어마어마하게 많은 클래스들이 있지만, 많은 부분이 데코레이터 패턴을 바탕으로 만들어져 있다.
|!10.jpg!|
* 스타 버즈 디자인하고 별로 다르지 않죠? 출력 스트림의 디자인도 똑같다.
* 자바 I/O를 보면 데코레이터의 단점도 발견 할 수 있다.
데코레이터 패턴을 이용해서 디자인을 하다 보면 잡다한 클래스들이 너무 많아 진다.
h3. 3.1 자바 I/O 데코레이터
{code:title=LowerCaseInputStream.java|borderStyle=solid}
//InputStream의 추상 데코레이터인 FilterInputStream을 확장 합니다.
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) {
super(in);
}
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c : Character.toLowerCase((char)c));
}
public int read(byte[] b, int offset, int len) throws IOException {
int result = super.read(b, offset, len);
for (int i = offset; i < offset+result; i++) {
b[i] = (byte)Character.toLowerCase((char)b[i]);
}
return result;
}
}
LowerCaseTest.java
public class LowerCaseTest {
public static void main(String[] args) {
int c;
try{
InputStream in =
new LowerCaseInputStream(
new BufferedInputStream(
new FileInputStream("C:/test.txt")));
while((c=in.read()) >= 0){
System.out.print((char)c);
}
in.close();
}catch(IOException ioe){
ioe.printStackTrace();
}
}
}