데코레이터 패턴은 객체에 동적으로 새로운 기능을 추가할 수 있도록 하는 패턴이다.
이 패턴은 상속을 사용하지 않고, 기존 코드 변경 없이 기능을 확장할 수 있도록 해준다.
Component(컴포넌트, 기본 인터페이스)
ConcreteComponent(구체적인 컴포넌트)
Decorator(데코레이터, 기본 컴포넌트를 감싸는 역할)
ConcreteDecorator(구체적인 데코레이터)
// 컴포넌트 (커피 인터페이스)
public abstract class Beverage {
String description = "알 수 없는 음료";
public String getDescription() {
return description;
}
public abstract double cost();
}
// 구체적인 컴포넌트 (에스프레소)
public class Espresso extends Beverage {
public Espresso() {
description = "에스프레소";
}
@Override
public double cost() {
return 1.99;
}
}
// 데코레이터 (추상 클래스)
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
// 구체적인 데코레이터 (우유 추가)
public class Milk extends CondimentDecorator {
Beverage beverage;
public Milk(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", 우유";
}
@Override
public double cost() {
return beverage.cost() + 0.30;
}
}
// 실행 코드
public class CoffeeShop {
public static void main(String[] args) {
Beverage espresso = new Espresso();
System.out.println(espresso.getDescription() + " $" + espresso.cost());
Beverage espressoWithMilk = new Milk(new Espresso());
System.out.println(espressoWithMilk.getDescription() + " $" + espressoWithMilk.cost());
}
}
실행 결과
에스프레소 $1.99
에스프레소, 우유 $2.29
상속보다 조합(Composition)이 강력한 이유
데코레이터 중첩 가능
new Milk(new Mocha(new Espresso())))스트림 API와 유사
BufferedInputStream, DataInputStream 등이 데코레이터 패턴을 활용한 대표적인 예시. InputStream을 감싸면서 추가 기능을 더하는 방식.| 구분 | 옵저버 패턴 (Observer Pattern) | 데코레이터 패턴 (Decorator Pattern) |
|---|---|---|
| 목적 | 객체 간 일대다 관계에서 상태 변경을 알림 | 객체에 새로운 기능을 동적으로 추가 |
| 구조 | Subject(발행자)와 Observer(구독자) 간의 관계 | 기존 객체를 감싸는(Wrapper) 방식 |
| 관계 | 느슨한 결합 (Subject는 Observer를 모름) | 원본 객체를 감싸면서 기능을 확장 |
| 사용 예시 | GUI 이벤트 리스너, 뉴스 구독 시스템 | Java I/O 스트림 (BufferedInputStream) |
| 단점 | 옵저버가 많아지면 업데이트 비용 증가 | 중첩이 많아지면 객체가 복잡해질 수 있음 |