

문제점:
public abstract class Beverage {
protected String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost(); // 모든 음료가 구현
}
왜 Abstract Class인가?
description 같은 공통 필드 제공public abstract class CondimentDecorator extends Beverage {
protected Beverage beverage; // 감쌀 대상 보관
public abstract String getDescription();
}
왜 Beverage를 상속하는가?
// Decorator는 Beverage를 '상속' (타입 통일)
public class Mocha extends CondimentDecorator {
// Beverage를 '조합' (기능 확장)
private Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
// 기존 행동에 새 행동 추가
public double cost() {
return 0.20 + beverage.cost(); // 위임 + 추가
}
}
상속의 목적:
Beverage beverage = new Mocha(new Espresso()) 가능조합의 목적:


- Decorator는 Component를 "상속" (같은 타입)
- Decorator는 Component를 "포함" (감싸기)
- 무한히 감쌀 수 있음: Whip(Mocha(Mocha(Espresso)))

// Component (기본 음료)
public abstract class Beverage {
protected String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
// 구체적인 음료들
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}
public double cost() {
return 1.99; // 에스프레소 가격
}
}
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "Dark Roast Coffee";
}
public double cost() {
return 0.99;
}
}
// Decorator 추상 클래스
public abstract class CondimentDecorator extends Beverage {
protected Beverage beverage; // 감쌀 음료
public abstract String getDescription();
}
// 모카 첨가물
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage; // 음료를 감쌈
}
public String getDescription() {
// 기존 설명에 모카 추가
return beverage.getDescription() + ", Mocha";
}
public double cost() {
// 기존 가격에 모카 가격 추가 (핵심!)
return 0.20 + beverage.cost();
}
}
// 휘핑크림 첨가물
public class Whip extends CondimentDecorator {
public Whip(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Whip";
}
public double cost() {
return 0.10 + beverage.cost();
}
}
// 두유 첨가물
public class Soy extends CondimentDecorator {
public Soy(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Soy";
}
public double cost() {
return 0.15 + beverage.cost();
}
}
public class StarbuzzCoffee {
public static void main(String[] args) {
// 1. 아무것도 추가하지 않은 에스프레소
Beverage beverage1 = new Espresso();
System.out.println(beverage1.getDescription()
+ " $" + beverage1.cost());
// 출력: Espresso $1.99
// 2. 다크 로스트 + 모카 + 모카 + 휘핑크림
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2); // 첫 번째 모카로 감싸기
beverage2 = new Mocha(beverage2); // 두 번째 모카로 감싸기
beverage2 = new Whip(beverage2); // 휘핑크림으로 감싸기
System.out.println(beverage2.getDescription()
+ " $" + beverage2.cost());
// 출력: Dark Roast Coffee, Mocha, Mocha, Whip $1.49
// 3. 하우스 블렌드 + 두유 + 모카 + 휘핑크림
Beverage beverage3 = new HouseBlend();
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println(beverage3.getDescription()
+ " $" + beverage3.cost());
// 출력: House Blend Coffee, Soy, Mocha, Whip $1.34
}
}
beverage2.cost() 호출 순서:
1. Whip.cost()
→ 0.10 + beverage.cost() 호출
2. Mocha.cost()
→ 0.20 + beverage.cost() 호출
3. Mocha.cost()
→ 0.20 + beverage.cost() 호출
4. DarkRoast.cost()
→ 0.99 반환
역순으로 계산:
0.99 + 0.20 + 0.20 + 0.10 = 1.49
// FileInputStream: Component (파일에서 바이트 읽기)
// BufferedInputStream: Decorator (버퍼링 기능 추가)
// LineNumberInputStream: Decorator (줄 번호 추가)
InputStream in =
new LineNumberInputStream( // 줄 번호 기능
new BufferedInputStream( // 버퍼링 기능
new FileInputStream("file.txt") // 기본 파일 읽기
)
);
// 소문자 변환 Decorator
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) {
super(in); // Component 저장
}
// 한 바이트 읽기 - 소문자로 변환
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;
}
}
public class InputTest {
public static void main(String[] args) throws IOException {
int c;
try {
// 여러 Decorator로 감싸기
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();
}
}
}
"확장에는 열려있고, 수정에는 닫혀있어야 한다"
| 요소 | 역할 | 특징 |
|---|---|---|
| Component | 기본 인터페이스 정의 | 원본과 장식된 객체 모두의 기반 타입 |
| ConcreteComponent | 실제 기본 객체 | 기능이 추가될 원본 객체 (Espresso, DarkRoast) |
| Decorator | 장식자 추상 클래스 | Component를 포함하고 상속함 |
| ConcreteDecorator | 실제 장식 기능 | 새로운 행동/책임 추가 (Mocha, Whip) |
타입 통일: Decorator가 Component와 같은 타입
Beverage beverage = new Mocha(new Espresso())위임 (Delegation): Decorator가 Component에게 작업 위임
public double cost() {
return 0.20 + beverage.cost(); // 위임 + 추가
}
재귀적 구조: Decorator를 Decorator로 감쌀 수 있음
Whip(Mocha(Mocha(Espresso)))
장점:
단점:
// Component
public abstract class Pizza {
protected String description = "Basic Pizza";
public String getDescription() {
return description;
}
public abstract double cost();
}
// ConcreteComponents
public class Margherita extends Pizza {
public Margherita() {
description = "Margherita Pizza";
}
public double cost() {
return 8.00;
}
}
// Decorators
public class ExtraCheese extends ToppingDecorator {
public ExtraCheese(Pizza pizza) {
this.pizza = pizza;
}
public String getDescription() {
return pizza.getDescription() + ", Extra Cheese";
}
public double cost() {
return 1.50 + pizza.cost();
}
}
// 사용
Pizza myPizza = new Margherita();
myPizza = new ExtraCheese(myPizza);
myPizza = new Olives(myPizza);
myPizza = new Mushrooms(myPizza);
System.out.println(myPizza.getDescription() + " $" + myPizza.cost());
// Margherita Pizza, Extra Cheese, Olives, Mushrooms $12.50