안녕하세요! GoF 디자인 패턴 연재, 이번 시간에는 구조 패턴(Structural Patterns)의 한 종류로, 기존 코드를 수정하지 않고도 객체에 새로운 기능을 덧붙일 수 있게 해주는 데코레이터(Decorator) 패턴에 대해 알아보겠습니다.
정의
데코레이터 패턴은 객체에 동적으로 새로운 책임(기능)을 추가할 수 있게 해주는 패턴입니다. 기능을 추가하기 위해 서브클래싱(상속)을 사용하는 것보다 더 유연한 대안이 될 수 있습니다. 이름 그대로 객체를 장식(Decorate)하는 것처럼 기능을 덧씌우는 방식입니다.
가장 쉬운 예는 커피 주문입니다. 기본 '아메리카노'가 있습니다. 여기에 '우유 추가', '모카 시럽 추가', '휘핑 크림 추가'와 같은 옵션들을 원하는 대로 선택하여 추가할 수 있습니다. 각 옵션(데코레이터)은 기존 커피에 새로운 맛과 가격을 더합니다. '우유를 추가한 아메리카노'에 다시 '모카 시럽'을 추가할 수도 있죠. 이처럼 객체를 여러 겹으로 감싸면서 점진적으로 기능을 확장하는 것이 데코레이터 패턴의 핵심입니다.
데코레이터 패턴은 다음과 같이 이루어져 있습니다.
- Component (컴포넌트): 장식될 기본 객체와 데코레이터들이 공통으로 구현할 인터페이스입니다. (예: Coffee)
- ConcreteComponent (구체적인 컴포넌트): 장식의 대상이 되는 핵심 기능을 가진 기본 객체입니다. (예: Americano)
- Decorator (데코레이터): Component 인터페이스를 구현하면서, 내부에 또 다른 Component 객체에 대한 참조를 가집니다. 이 참조가 바로 자신이 장식할 객체입니다.
- ConcreteDecorator (구체적인 데코레이터): Decorator의 서브클래스로, 실제로 새로운 책임(기능)을 추가하는 역할을 합니다. (예: WithMilk, WithMocha)
⌨️ 예제 코드 (Example)
앞서 설명한 커피 주문 예제를 자바(Java) 코드로 구현해 보겠습니다.
1. 컴포넌트 (Component) 인터페이스 정의
모든 커피 메뉴가 공통으로 가질 기능(가격 계산, 설명)의 인터페이스를 정의합니다.
// Component 인터페이스
public interface Coffee {
double getCost();
String getDescription();
}
2. 구체적인 컴포넌트 (ConcreteComponent) 클래스 구현
기본 커피인 Americano 클래스를 구현합니다.
// ConcreteComponent 클래스
public class Americano implements Coffee {
@Override
public double getCost() {
return 4.0; // 기본 가격 4.0
}
@Override
public String getDescription() {
return "아메리카노";
}
}
3. 데코레이터 (Decorator) 추상 클래스 구현
모든 데코레이터의 기반이 될 추상 클래스를 정의합니다.
// Decorator 추상 클래스
public abstract class CoffeeDecorator implements Coffee {
// 자신이 장식할 대상(Component)
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
// 기본적으로는 장식 대상에게 위임
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
}
4. 구체적인 데코레이터 (ConcreteDecorator) 클래스 구현
실제 추가 옵션인 WithMilk와 WithMocha를 구현합니다.
// ConcreteDecorator A: 우유 추가
public class WithMilk extends CoffeeDecorator {
public WithMilk(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
// 기본 가격에 우유 가격 추가
return super.getCost() + 0.5;
}
@Override
public String getDescription() {
// 기본 설명에 "우유 추가" 덧붙이기
return super.getDescription() + ", 우유 추가";
}
}
// ConcreteDecorator B: 모카 시럽 추가
public class WithMocha extends CoffeeDecorator {
public WithMocha(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
// 기본 가격에 모카 시럽 가격 추가
return super.getCost() + 0.7;
}
@Override
public String getDescription() {
// 기본 설명에 "모카 시럽 추가" 덧붙이기
return super.getDescription() + ", 모카 시럽 추가";
}
}
5. 클라이언트 (Client) 코드
클라이언트는 기본 커피에 원하는 데코레이터를 여러 겹으로 감싸서 사용합니다.
public class CoffeeShop {
public static void main(String[] args) {
// 1. 기본 아메리카노 주문
Coffee coffee = new Americano();
System.out.println("주문: " + coffee.getDescription() + " / 가격: " + coffee.getCost());
// 2. 아메리카노에 우유 추가
coffee = new WithMilk(coffee);
System.out.println("주문: " + coffee.getDescription() + " / 가격: " + coffee.getCost());
// 3. 우유 추가된 커피에 모카 시럽도 추가
coffee = new WithMocha(coffee);
System.out.println("주문: " + coffee.getDescription() + " / 가격: " + coffee.getCost());
// 실행 결과:
// 주문: 아메리카노 / 가격: 4.0
// 주문: 아메리카노, 우유 추가 / 가격: 4.5
// 주문: 아메리카노, 우유 추가, 모카 시럽 추가 / 가격: 5.2
}
}
결론
- 상속을 사용하지 않고, 객체에 동적으로 새로운 기능을 추가하거나 제거하고 싶을 때
- 많은 수의 독립적인 확장 기능이 있고, 이들의 조합이 매우 다양할 때: 상속으로 모든 조합을 만들면 클래스가 폭발적으로 증가하지만, 데코레이터는 필요한 만큼만 조합하여 사용할 수 있습니다.
- 기존 코드를 수정하는 것이 불가능할 때(OCP 준수): 원본 클래스를 변경하지 않고 래퍼(Wrapper) 클래스를 사용하여 기능을 확장할 수 있습니다.
중첩적이고 Wrapper를 이용해 높은 독립성을 유지할 수 있지만 다음의 사항을 고려해야합닝다
- 많은 수의 작은 객체들: 데코레이터를 많이 사용할수록 관리해야 할 작은 클래스들이 많아져 시스템이 복잡해 보일 수 있습니다.
- 디버깅의 어려움: 객체가 여러 데코레이터에 감싸여 있을 때, 특정 데코레이터의 동작을 추적하고 디버깅하기가 까다로울 수 있습니다.
- 특정 컴포넌트의 타입을 확인하기 어려움: 장식된 객체는 데코레이터에 의해 감싸여 있어, 원래의 ConcreteComponent가 무엇이었는지 알기 어렵습니다.
데코레이터 패턴은 객체의 핵심 기능은 그대로 둔 채, 필요한 기능들을 마치 옷을 겹쳐 입듯이 유연하게 추가할 수 있게 해주는 강력한 패턴입니다. Java의 I/O 클래스(FileInputStream, BufferedInputStream 등)가 이 패턴의 대표적인 활용 사례입니다. 다음 시간에는 Flyweight 패턴에 대해서 알아보겠습니다.
'정보보안-이론 > XX에 대하여' 카테고리의 다른 글
[디자인 패턴] (GoF 구조패턴) Composite Pattern에 대하여 (0) | 2025.07.07 |
---|---|
[디자인 패턴] (GoF 구조패턴) Bridge Pattern에 대하여 (0) | 2025.07.06 |
[디자인 패턴] (GoF 구조패턴) Adapter Pattern에 대하여 (0) | 2025.07.06 |
[디자인 패턴] (GoF 생성패턴) Singleton Pattern에 대하여 (0) | 2025.07.04 |
[디자인 패턴] (GoF 생성패턴) Builder Pattern에 대하여 (0) | 2025.07.03 |