데코레이터가 꾸밀 대상을 구성으로 가지고 있기 때문에 행동(비용 계산)을 실행중에 동적으로 설정할 수 있음.
음료에 첨가물이 추가되도 유연성을 잃지 않음.
커피 주문 시스템 코드 만들기
더블 모카에 두유와 휘핑 크림을 추가한 음료를 만든다면?
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();
}
첨가물은 음료 객체를 상속함.
데코레이터가 감쌀 음료 객체를 구성으로 가짐.
모든 음료 객체를 감쌀 수 있도록 음료 객체 슈퍼 클래스를 가짐
음료 코드 구현하기
public class Espresso extends Beverage {
public Espresso() {
description = "에스프레소";
}
public double cost() {
return 1.99;
}
}
실제 음료인 에스프레소는 description 변수를 설정함.
에스프레소 가격을 계산함.
첨가물 가격을 걱정할 필요가 없음.
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "하우스 블렌드 커피";
}
public double cost() {
return .89;
}
}
에스프레소와 거의 동일함.
첨가물 코드 구현하기
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", 모카";
}
public double cost() {
return beverage.cost() + .20;
}
}
첨가물 구상체는 인스턴스 변수로 Beverage 를 구성으로 가짐
생성 시에 설정함.
음료 설명에 첨가되는 아이템 설명이 추가됨.
음료 가격에 모카 가격을 추가하여 계산함.
커피 주문 시스템 코드 테스트
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());
}
}
감쌀 대상인 Beverage 를 먼저 선언하고, 이를 첨가물 생성자를 통해 계속 감싸주기만 하면 됨.
데코레이터가 적용된 예 : 자바 I/O
FileInputStream 은 데코레이터로 장식할 예정인 구상 객체임. → 추상 구성 요소는 InputStream.
BufferedInputStream은 구상 데코레이터로, FileInputStream에 입력을 미리 읽어서 더 빠르게 처리할 수 있게 해주는 버퍼링 기능을 더해 주는 역할임
ZipInputStream도 구상 데코레이터 → zip 파일에서 데이터를 읽어 올 때, 그 속에 있는 항목을 읽는 기능임.
추상 데코레이터는 FilterInputStream.
출력 스트림 디자인도 똑같음.
이러한 데코레이터 패턴은, 잡다한 클래스가 너무 많아짐.
데코레이터 패턴이 적용된 API를 사용하는 개발자는 괴로움.
하지만, 데코레이터 패턴이 어떤 식으로 작동하는지 이해하면, 데코레이터 패턴으로 감싸서 원하는 행동을 구현할 수 있음.
자바 I/O 데코레이터 만들기
입력 스트림에 있는 대문자를 전부 소문자로 바꿔주는 데코레이터를 만든다면?
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) {
super(in);
}
public int read() throws IOException {
int c = in.read();
return (c == -1 ? c Character.toLowerCase((char)c));
}
public int read(byte[] b, int offset, int len) throws IOException {
int result = in.read(b, offset, len);
for (int i = offset; i < offset+result; i++) {
b[i] = (byte) Character.toLowerCase((char)b[i]);
}
return result;
}
}
public class InputTest {
public static void main(String[] args) throws IOException {
int c;
try {
InputStream in =
new LowerCaseInputStream(
new BufferedInputStream(
new FileInputStream("test.txt")));
while((c = in.read()) >= 0) {
System.out.print((char)c);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}