

데코레이터(Decorator) 패턴 정리
DesignPattern
2025.11.17.
1. 데코레이터 패턴
기존 코드를 변경하지 않고 부가기능을 추가하는 패턴
- 객체의 결합을 통해 기능을 동적으로 유연하게 확장할 수 있게 해주는 패턴
- 동적으로 유연하게 확장이 가능하다 -> 런타임 시 부가기능을 추가하는 것도 가능하다는 의미
- 객체의 결합이란 상속이 아닌, 위임 즉, Has-a관계를 가지는 패턴을 의미
- 데코레이터 패턴은 Decorator 객체를 조합함으로써 추가 기능의 조합을 설계하는 패턴
Tip! - 합성 vs 위임
합성
객체 안에 다른 객체를 포함하는 구조
class A {
B b; // 포함 = 합성
}
위임
포함된 그 객체에게 일을 넘겨서 실행하게 만드는 행동 방식
class A {
B b;
void doSomething() {
b.doSomething(); // 위임(delegate)
}
}

- Component : 원본 객체와 장식된 객체 모두를 묶는 역할
- ConcreateComponent : 원본 객체
- Decorator : 추상화 된 장식자 클래스
- 원본 객체를 합성(Composition)한 wrapper 필드와 인터페이스의 구현 메소드를 가짐
- ConcreateDecorator : 구체적인 장식자 클래스
- 부모 클래스가 감싸고 있는 하나의 Component를 호출하면서 호출 전/후로 부가적인 로직을 추가
- Decorator는 Component를 멤버 변수로 포함(=합성) 하여 감싸고, Component가 가진 기능(Operation)을 그대로 제공한다.
- 기능 확장 시 Component를 직접 상속하지 않고 Decorator를 상속함으로써 동일한 인터페이스 구조에서 기능을 추가할 수 있다.
- 즉, Decorator는 부모 인터페이스(Component)를 내부에 가지고 있어 외부에서 의존성 주입이 가능하고, 기능을 확장한 Decorator(자식)는 이 부모 기능을 활용해 기존 기능 + 확장 기능을 만들 수 있다.
=> 결과적으로 확장된 Decorator를 외부에서 조립(주입)하면서 기능을 점진적으로 덧붙이는 구조가 된다.
즉, 데코레이터는 내부에 원본 객체를 합성(composition) 방식으로 들고 있고, 메서드 호출은 그 원본 객체에 위임한다.
class ToppingDecorator implements IceCream {
protected IceCream inner; // 합성
@Override
public String describe() {
return inner.describe(); // 위임
}
}
즉, 데코레이터 = 합성 + 위임 + 기능 덧입히기
2. 데코레이터 패턴 구현
- 상속으로 기능을 확장 -> 데코레이터 패턴 적용
상속으로 기능 확장 예시
public class Client {
private CommentService commentService;
public Client(CommentService commentService) {
this.commentService = commentService;
}
public void writeComment(String comment) {
commentService.addComment(comment);
}
}
public class CommentService {
public void addComment(String comment) {
System.out.println(comment);
}
}
public class TrimmingCommentService extends CommentService{
@Override
public void addComment(String comment) {
super.addComment(trim(comment));
}
private String trim(String comment) {
return comment.replace("!", "~");
}
}
public class SpamFilteringCommentService extends CommentService{
@Override
public void addComment(String comment) {
if (isNotSpam(comment)) {
super.addComment(comment);
}
}
private boolean isNotSpam(String comment) {
return !comment.contains("http");
}
}
public class App {
public static void main(String[] args) {
Client client = new Client(new CommentService());
client.writeComment("오징어 게입 수위 미쳤다ㄷㄷ → http://광고광고.com");
client.writeComment("내가 왕이 될 상인가");
client.writeComment("내가임마!!! 어이!!");
System.out.println("--------- ");
Client trimClient = new Client(new TrimmingCommentService());
trimClient.writeComment("오징어 게입 수위 미쳤다ㄷㄷ → http://광고광고.com");
trimClient.writeComment("내가 왕이 될 상인가");
trimClient.writeComment("내가임마!!! 어이!!");
System.out.println("--------- ");
Client spamFilterClient = new Client(new SpamFilteringCommentService());
spamFilterClient.writeComment("오징어 게입 수위 미쳤다ㄷㄷ → http://광고광고.com");
spamFilterClient.writeComment("내가 왕이 될 상인가");
spamFilterClient.writeComment("내가임마!!! 어이!!");
}
}
- 상속을 통한 기능 확장의 단점
- trim기능과 spam기능을 동시에 적용하고 싶으면 추가적인 구현 클래스를 다시 만들어야 한다. (TrimSpamFilteringComment~)
- 이러한 단점을 해결하기 위해 Decorator 객체를 조합함으로써 추가 기능의 조합을 설계하는 데코레이터 패턴을 사용
데코레이터 패턴 적용 예시
public class Client {
private CommentService commentService;
public Client(CommentService commentService) {
this.commentService = commentService;
}
public void writeComment(String comment) {
commentService.addComment(comment);
}
}
public interface CommentService {
void addComment(String comment);
}
public class DefaultCommentService implements CommentService{
@Override
public void addComment(String comment) {
System.out.println(comment);
}
}
public class App {
public static void main(String[] args) {
CommentService commentService = new DefaultCommentService();
Client client = new Client(commentService);
client.writeComment("오징어 게입 수위 미쳤다ㄷㄷ → http://광고광고.com");
client.writeComment("내가 왕이 될 상인가");
client.writeComment("내가임마!!! 어이!!");
}
}
- CommentService를 인터페이스를 기능을 분리
public class CommentDecorator implements CommentService {
private CommentService commentService;
public CommentDecorator(CommentService commentService) {
this.commentService = commentService;
}
@Override
public void addComment(String comment) {
commentService.addComment(comment);
}
}
public class TrimmingCommentDecorator extends CommentDecorator {
public TrimmingCommentDecorator(CommentService commentService) {
super(commentService);
}
@Override
public void addComment(String comment) {
super.addComment(trim(comment));
}
private String trim(String comment) {
return comment.replace("!", "~");
}
}
public class SpamFilteringCommentDecorator extends CommentDecorator {
public SpamFilteringCommentDecorator(CommentService commentService) {
super(commentService);
}
@Override
public void addComment(String comment) {
if (isNotSpam(comment)) {
super.addComment(comment);
}
}
private boolean isNotSpam(String comment) {
return !comment.contains("http");
}
}
- 확장된 기능을 Overriding 한 각각 구현체에서 부모의 기능을 참조하여 기능을 확장
/**
* 데코레이터패턴을 사용하여 상속이 아닌 위임으로 문제를 해결
*/
public class App {
private static boolean enabledSpamFilter = true;
private static boolean enableTrimming = true;
public static void main(String[] args) {
CommentService commentService = new DefaultCommentService();
if (enabledSpamFilter) {
commentService = new SpamFilteringCommentDecorator(commentService);
}
if (enableTrimming) {
commentService = new TrimmingCommentDecorator(commentService);
}
Client client = new Client(commentService);
client.writeComment("오징어 게입 수위 미쳤다ㄷㄷ → http://광고광고.com");
client.writeComment("내가 왕이 될 상인가");
client.writeComment("내가임마!!! 어이!!");
}
}
3. 패턴 사용시기와 장, 단점
패턴 사용 시기
- 객체 책임과 행동이 동적으로 상황에 따라 다양한 기능이 빈번하게 추가/삭제 되는 경우
- 객체의 결합을 통해 기능이 생성될 수 있는 경우
- 객체를 사용하는 코드를 손상시키지 않고 런타임에 객체에 추가 동작을 할당할 수 있어야 하는 경우
- 상속을 통해 서브 클래싱으로 객체 동작을 확장하는 것이 어색하거나 불가능 할 때
장점
- 서브클래스를 만들 때 보다 더 유연하게 기능 확장
- 객체를 여러 데코레이터로 래핑해 여러 동작 결합 가능
- 컴파일 타임이 아닌 런타임에 동적으로 기능 변경 가능
- 장식자 클래스마다 고유의 책임을 가져 단일책임원칙(SRP)를 준수
- 클라이언트 코드 수정 없이 기능 확장이 필요하면 장식자 클래스를 추가하면 되니 개방 폐쇄 원칙(OCP) 준수
- 구현체가 아닌 인터페이스를 바라봄으로써 의존 역전 원칙(DIP) 준수
단점
- 장식자 일부를 제거하고 싶으면, Wrapper 스택에서 특정 wrapper를 제거하는 것이 어렵다
- 데코레이터를 조합하는 초기 생성 코드가 보기 안좋을 수 있다. (new A(new B(new C())))
- 순서에 따라 데코레이터 스택 순서가 결정되기에 순서에 의존하지 않는 방식으로 데코레이터를 구현하기 어렵다
4. 실무에서 찾아보는 Decorator 패턴
Java
- InputStream, OutputStream, Reader, Writer의 생성자를 활용한 랩퍼
- java.util.Collections가 제공하는 메소드들 활용한 랩퍼
- javax.servlet.http.HttpServletRequest / ResponseWrapper
- java.io.InputStream, OutputStream, Reader, Writer의 모든 하위 클래스에 동일한 유형의 인스턴스를 사용하는 생성자
- java.util.Collections의 checkedXXX(), synchronizedXXX(), unmodifiableXXX() 메서드들
- javax.servlet.http.HttpServletRequestWrapper 그리고 HttpServletResponseWrapper
- javax.swing.JScrollPane
자바 I/O 메서드
- InputStream, OutputStream, Reader, Writer의 생성자를 활용한 파일 I/O 랩퍼 부분은 데코레이터 패턴의 대표적인 예
- 자바 코드에서 파일을 읽어 들일 때 다음과 같이 객체 생성자를 중첩하여 사용
- File → FileReader → BufferedReader 순으로 갈수록 점점 부가 기능이 추가 됨
Spring Framework
- HttpServletRequestWrapper / HttpServletResponseWrapper
- HttpServletRequest를 확장해서 HttpServletRequestWrapper가 제공하는 기능을 오버라이딩해서 부가적인 기능을 추가 가능
- 예를들어 HTTP 요청 메시지 본문을 다르게 처리. 캐싱, 로깅, 의심스러운 요청 확인 등의 작업을 해야할 때, 이런 wrapper를 만들어서 사용
- HttpServletRequest를 확장해서 HttpServletRequestWrapper가 제공하는 기능을 오버라이딩해서 부가적인 기능을 추가 가능
- BeanDefinitionDecorator
- 빈(Bean) 설정 데코레이터로 스프링의 인프라로 등록
- ServerHttpRequestDecorator / ServerHttpResponseDecorator
- Webflux HTTP 요청/응답 데코레이터
- 이 데코레이터를 상속받는 클래스를 만들어서 WebFilter를 거쳐가는 모든 요청이 이 데코레이터의 하위 클래스를 거쳐가게 함