데코레이터 패턴 (Decorator)
데코레이터 패턴(Decorator Pattern)은 객체의 뼈대(원본)는 그대로 둔 채, 런타임에 동적으로 객체에 새로운 책임이나 기능(장식)을 유연하게 추가할 수 있는 구조 패턴입니다.
카페에서 '에스프레소'라는 기본 커피 객체에 '물'을 감싸면 아메리카노가 되고, 거기에 다시 '우유'를 감싸면 라떼가 되는 원리입니다. 상속을 통해 `LatteCoffee`, `MochaCoffee` 클래스를 수없이 만드는 대신, 장식자(Decorator)들을 양파 껍질처럼 계속 씌우는 방식으로 결합도를 크게 낮춥니다. Java의 I/O 스트림(`BufferedReader(new FileReader(...))`)이 이 패턴의 대표적인 적용 사례입니다.
<!-- ==========================================
// 📂 Beverage & Base (원소)
// ==========================================
// 기본 인터페이스
interface Beverage {
String getDescription();
int getCost();
}
// 구체적인 기본 음료 (장식이 될 뼈대)
class Espresso implements Beverage {
public String getDescription() { return "에스프레소"; }
public int getCost() { return 2000; }
}
// ==========================================
// 📂 Decorator (장식자)
// ==========================================
// 1. 장식자도 Beverage를 구현합니다. (자신이 음료인 척 함)
abstract class BeverageDecorator implements Beverage {
// 2. 자신이 감쌀 또 다른 Beverage 객체를 내부에 가집니다.
protected Beverage wrappedBeverage;
public BeverageDecorator(Beverage beverage) {
this.wrappedBeverage = beverage;
}
}
// 구체적인 장식자들
class Milk extends BeverageDecorator {
public Milk(Beverage beverage) { super(beverage); }
public String getDescription() {
return wrappedBeverage.getDescription() + ", 스팀 밀크";
}
public int getCost() {
return wrappedBeverage.getCost() + 500; // 원본 가격 + 우유 가격
}
}
class Mocha extends BeverageDecorator {
public Mocha(Beverage beverage) { super(beverage); }
public String getDescription() {
return wrappedBeverage.getDescription() + ", 초코 모카";
}
public int getCost() {
return wrappedBeverage.getCost() + 800;
}
}
// ==========================================
// 📂 Main.java (사용 예시)
// ==========================================
public class Main {
public static void main(String[] args) {
// 1. 기본 에스프레소 주문
Beverage myCoffee = new Espresso();
// 2. 우유 추가 (에스프레소를 감쌉니다)
myCoffee = new Milk(myCoffee);
// 3. 모카 시럽 2번 추가 (다시 껍질을 계속 감쌉니다)
myCoffee = new Mocha(myCoffee);
myCoffee = new Mocha(myCoffee);
// 결과는 안에서부터 밖으로 연쇄적으로 실행되어 합산됩니다.
System.out.println("주문 내역: " + myCoffee.getDescription());
System.out.println("총 가격: " + myCoffee.getCost() + "원");
// 출력: 주문 내역: 에스프레소, 스팀 밀크, 초코 모카, 초코 모카
// 출력: 총 가격: 4100원
}
}