프록시 패턴과 구조적으로 유사한 디자인 패턴들이 존재하며, 이들은 의도에 따라 서로 구분되기도 합니다. 예를 들어, 프록시 패턴은 접근 제어나 로깅과 같은 제어 책임을 추가하는 데 목적이 있는 반면, 데코레이터 패턴은 기능을 유연하게 확장하는 데 그 목적이 있습니다. 이번 글에서는 이러한 의도 차이를 바탕으로 데코레이터 패턴(Decorator Pattern)에 대해 정리해 보겠습니다.
데코레이터 패턴 ( Decorator Pattern )
객체 지향 프로그래밍 에서 데코레이터 패턴은 같은 클래스의 다른 인스턴스의 동작에 영향을 주지 않고 개별 객체에 동적으로 동작을 추가할 수 있게 해주는 디자인 패턴입니다.
이 패턴은 기능을 고유한 관심 영역을 가진 클래스 간에 나눌 수 있으므로 단일 책임 원칙을 준수하는 데 종종 유용합니다. 또한 클래스의 기능을 수정하지 않고도 확장할 수 있으므로 개방-폐쇄 원칙을 준수하는데 유용합니다.
데코레이터를 사용하면 완전히 새로운 객체를 정의하지 않고도 객체의 동작을 확장할 수 있으므로 서브 클래싱보다 효율적일 수 있습니다.
(Wikipedia:데코레이터 패턴)
데코레이터 패턴의 구조

데코레이터 패턴은 기존 객체(ConcreteComponent)에 기능을 동적으로 추가하거나 확장하고 싶을 때 사용하는 구조로 상속이 아닌 구성을 통해 유연하게 기능을 확장할 수 있따는 점이 큰 특징입니다.
✅ 주요 구성 요소
- Compoent (컴포넌트 인터페이스)
- 공통 인터페이스로 모든 ConcreteComponent와 Decorator는 이 인터페이스를 구현합니다.
- ConcreteCompoent (구체 컴포넌트)
- 실제 기본 기능을 수행하는 클래스로 여기에 추가적인 기능을 덧붙이고 싶을 때 데코레이터를 활용 합니다.
- Decorator (추상 데코레이터)
- Compoent를 구현하고, 내부에 Compoent를 포함하고 있습니다.
- 기본 구조는 그대로 유지한 채, 자식 클래스에서 기능을 확장하거나 가공할 수 있도록 합니다.
- ConcreteDecorator (구체 데코레이터)
- Decorator를 상속받아 기존 기능에 부가 기능을 추가합니다
📜 흐름 요약
- 클라이언트는 Compoent 인터페이스를 통해 동작을 요청
- 실제 구현은 ConcreteCompoent가 담당
- 필요시 ConcreteDecorator가 이를 감싸서 기능을 추가
- 여러 데코레이터를 중첩하여 다양한 조합으로 기능을 구성
데코레이터 패턴 예제 코드 : 텍스트 출력 꾸미기
이번 예제는 입력 받은 텍스트를 출력할때 꾸며주는 기능을 추가해보도록 하겠습니다.
// component
public interface Message {
String getContent();
}
Message (컴포넌트)
- 모든 메세지 객체가 구현해야 하는 공통 메서드 입니다
// Concrete component
public class SimpleMessage implements Message {
private String content;
public SimpleMessage(String content) {
this.content = content;
}
@Override
public String getContent() {
return content;
}
}
// Decorator
public abstract class MessageDecorator implements Message {
protected final Message message;
public MessageDecorator(Message message) {
this.message = message;
}
}
SimpleMessage (Concrete component)
- 데코레이터 없이 단독으로 사용할 수 있는 기본 메세지 구현체 입니다
MessageDecorator (Decorator)
- 다른 Message 객체를 감싸서 기능을 확장하는 기반 클래스 입니다
// Concrete decorator (반복 데코레이터)
public class RepeatDecorator extends MessageDecorator {
public RepeatDecorator(Message message) {
super(message);
}
@Override
public String getContent() {
return message.getContent() + " " + message.getContent();
}
}
// Concrete decorator (별표 데코레이터)
public class StarDecorator extends MessageDecorator {
public StarDecorator(Message message) {
super(message);
}
@Override
public String getContent() {
return "*" + message.getContent() + "*";
}
}
RepeatDecorator
- 메세지 내용을 한 번 더 반복해서 출력하는 데코레이터
StarDecorator
- 메세지 내용을 "*"로 감싸는 데코레이터
// 기본 메세지 생성
Message message = new SimpleMessage("Hello FIVE");
// 1. 반복 데코레이터
Message repeatDecorator = new RepeatDecorator(message);
System.out.println(repeatDecorator.getContent());
// 출력 : Hello FIVE Hello FIVE
// 2. 별 데코레이터
Message startDecorator = new StarDecorator(message);
System.out.println(startDecorator.getContent());
// 출력 : *Hello FIVE*
// 3. 중첩 데코레이터 - 1
Message repeatAndStartDecorator = new RepeatDecorator(new StarDecorator(message));
System.out.println(repeatAndStartDecorator.getContent());
// 출력 : *Hello FIVE* *Hello FIVE*
// 4. 중첩 데코레이터 - 2
Message startAndRepeatDecorator = new StarDecorator(new RepeatDecorator(message));
System.out.println(startAndRepeatDecorator.getContent());
// 출력 : *Hello FIVE Hello FIVE*
예제 1번
- RepeatDecorator는 내부 message.getContent() 값을 두 번 반복해서 반환합니다.
예제 2번
- StarDecorator는 메세지 앞뒤에 별표 *를 붙여 반환합니다.
예제 3번
- 먼저 StarDecorator가 적용되어 "*Hello FIVE*"가 되고, 그 뒤에 RepeatDecorator가 두 번 반복
예제 4번
- 먼저 RepeatDecorator가 적용 되어 "Hello FIVE Hello FIVE"가 되고, 그 뒤에 StarDecorator가 앞뒤에 *을 붙임
데코레이터 패턴의 특징, 장단점
- 기존 객체에 기능을 동적으로 추가할 수 있는 패턴
상속 없이 런타임에 객체의 동작을 유연하게 확장할 수 있음 - 구성(Composition)을 활용한 유연한 설계
데코레이터는 원래 객체를 감싸는 방식으로, 객체를 조립하듯 기능을 더할 수 있음 - OCP(Open-Closed Principle) 준수
기존 코드를 수정하지 않고 기능을 확장할 수 있어 유지보수가 용이함
🌟 장점
- 기능 추가의 유연성
상속 없이 런타임에 객체의 동작을 유연하게 확장할 수 있음 - 단일 책임 원칙(SRP) 적용 가능
데코레이터는 원래 객체를 감싸는 방식으로, 객체를 조립하듯 기능을 더할 수 있음
⚠️ 단점
- 중첩 구조로 인해 코드 추적이 어려움
데코레이터가 여러 단계로 중첩되면 실제 실행되는 로직이 복잡하게 느껴질 수 있음 - 디버깅과 테스트 복잡도 증가
여러 데코레이터가 조합될 경우, 어느 데코레이터가 어떤 역할을 하는지 파악하기 어려워질 수 있음 - 객체 생성이 많아짐
데코레이터를 여러 번 감싸야 하므로 인스턴스 수가 증가할 수 있음
📌 정리
데코레이터 패턴은 유연한 기능 확장이 필요한 경우에 유용한 패턴입니다. 특히, 기능 조합의 다양성과 OCP 준수가 필요한 상황에 적합합니다. 하지만 구조가 복잡해질 가능성이 있으므로, 간단한 기능 확장이나 데코레이터의 중첩이 많지 않을 때 효과적으로 사용할 수 있습니다.
'개발' 카테고리의 다른 글
| 리플렉션을 쓰기 전에 생각해야 하는 것들 (0) | 2025.05.24 |
|---|---|
| Spring AOP를 이해하기 전 : 프록시 패턴 (0) | 2025.05.20 |
| 전략 패턴을 더 간결하게: 템플릿 콜백 패턴 (0) | 2025.05.19 |