핵심 인사이트 (3줄 요약)
- 본질: 데코레이터 패턴 (Decorator Pattern)은 GoF 구조 패턴으로, 객체를 런타임에 동적으로 새로운 책임(기능)을 추가하는 패턴이다. 상속(Inheritance) 대신 구성(Composition)과 위임(Delegation)을 사용하여 기존 클래스를 변경하지 않고 기능을 확장한다.
- 가치: 클래스 수의 폭발적 증가 없이 런타임에 기능을 조합할 수 있다. Java IO 스트림(
BufferedInputStream(new FileInputStream(...)))처럼 기능 조합이 자유롭고 SRP·OCP를 동시에 달성한다.- 판단 포인트: 데코레이터는 '같은 인터페이스를 구현하는 래퍼(Wrapper)'라는 점이 핵심이다. 원본 객체와 동일한 인터페이스를 가져야 클라이언트 코드를 수정하지 않고도 데코레이터로 투명하게 교체할 수 있다. 프록시 패턴과의 차이는 목적이다: 데코레이터는 기능 추가, 프록시는 접근 제어.
Ⅰ. 개요 및 필요성
상속으로 기능을 확장하면 모든 조합에 대한 서브클래스가 필요하다. 예를 들어 커피(Coffee)에 우유(Milk)·설탕(Sugar)·휘핑크림(Whip)을 추가한다면, 상속으로는 MilkCoffee, SugarCoffee, MilkSugarCoffee, MilkSugarWhipCoffee 등 2^n개의 서브클래스가 필요하다.
데코레이터 패턴은 이 '서브클래스 폭발' 문제를 해결한다. new WhipDecorator(new SugarDecorator(new MilkDecorator(new Coffee())))처럼 런타임에 자유롭게 기능을 조합할 수 있다.
┌─────────────────────────────────────────────────────────────┐
│ 데코레이터 패턴 구조 │
├─────────────────────────────────────────────────────────────┤
│ Component (인터페이스) │
│ + operation(): void │
│ ▲ ▲ │
│ ConcreteComponent Decorator (추상) │
│ (원본 객체) - component: Component │
│ + operation(): void + operation(): void { │
│ component.operation(); │
│ } // 위임 + 추가 기능 │
│ ▲ │
│ ConcreteDecoratorA, ConcreteDecoratorB │
└─────────────────────────────────────────────────────────────┘
- 📢 섹션 요약 비유: 아이스크림(원본 객체)에 토핑(데코레이터)을 하나씩 올리는 방식이다. 아이스크림 자체를 바꾸지 않고 런타임에 초콜릿·견과류·크림을 자유롭게 조합한다.
Ⅱ. 아키텍처 및 핵심 원리
Java IO는 데코레이터 패턴의 교과서적 구현이다. InputStream이 Component, FileInputStream·ByteArrayInputStream이 ConcreteComponent, FilterInputStream이 Decorator 추상 클래스, BufferedInputStream·DataInputStream·GZIPInputStream이 ConcreteDecorator다.
// 데코레이터 체이닝 예시
InputStream is = new GZIPInputStream(
new BufferedInputStream(
new FileInputStream("data.gz")
)
);
| 항목 | 설명 | 포인트 |
|---|---|---|
| Component | 공통 인터페이스 | InputStream |
| ConcreteComponent | 원본 기능 구현 | FileInputStream |
| Decorator | 위임 + 기능 추가 | FilterInputStream |
| ConcreteDecorator | 특정 기능 추가 | BufferedInputStream |
┌─────────────────────────────────────────────────────────────┐
│ 데코레이터 체이닝 흐름 │
├─────────────────────────────────────────────────────────────┤
│ GZIPInputStream.read() │
│ └── BufferedInputStream.read() (버퍼링 처리) │
│ └── FileInputStream.read() (실제 파일 읽기) │
│ │
│ 각 데코레이터가 책임을 추가하고 원본에 위임 │
└─────────────────────────────────────────────────────────────┘
- 📢 섹션 요약 비유: 러시아 마트료시카 인형처럼, 바깥 인형(데코레이터)이 안쪽 인형(원본)을 감싸며 추가 기능을 더한다. 각 인형은 동일한 '인형 인터페이스'를 갖는다.
Ⅲ. 비교 및 연결
데코레이터와 프록시 패턴은 구조가 동일하지만 의도가 다르다. 데코레이터는 기능을 동적으로 추가하기 위한 것이고, 프록시는 접근 제어(캐싱, 로깅, 보안)를 위한 것이다.
| 비교 축 | A | B |
|---|---|---|
| 목적 | 기능 추가 | 접근 제어 |
| 구성 방식 | 동적 (런타임 조합) | 정적 (컴파일 타임 또는 프레임워크) |
| 대표 예시 | Java IO Stream | Spring AOP |
| 클라이언트 인식 | 데코레이터 존재 알 수 있음 | 프록시 존재 모름 |
- 📢 섹션 요약 비유: 데코레이터는 옷(기능)을 레이어로 입는 것이고, 프록시는 비서(접근 제어)를 두어 모든 연락을 비서가 처리하게 하는 것이다.
Ⅳ. 실무 적용 및 기술사 판단
스프링 AOP(Aspect-Oriented Programming)는 데코레이터(프록시) 패턴의 프레임워크 수준 구현이다. @Transactional, @Cacheable, @Aspect가 원본 Bean 메서드에 트랜잭션·캐싱·로깅 등의 관심사(Concern)를 동적으로 추가한다.
판단 체크리스트
- 데코레이터가 Component와 동일한 인터페이스를 구현하여 클라이언트 코드가 투명하게 사용하는가?
- 기능 추가가 상속이 아닌 구성(Composition)과 위임(Delegation)으로 이루어지는가?
- 데코레이터 체이닝이 올바른 순서로 적용되어 있는가?
- 프록시 패턴과 데코레이터 패턴을 혼동하지 않고 목적에 맞게 사용하는가?
- 서브클래스 폭발 문제를 데코레이터로 해결하고 있는가?
- 📢 섹션 요약 비유: 피자에 토핑을 추가하듯, 기존 서비스(피자)를 수정하지 않고 로깅·캐싱·트랜잭션(토핑) 데코레이터를 감싸서 기능을 동적으로 추가한다.
Ⅴ. 기대효과 및 결론
데코레이터 패턴을 적용하면 클래스 수의 폭발 없이 다양한 기능 조합이 가능하고, SRP(각 데코레이터가 단일 책임)와 OCP(기존 클래스 수정 없이 기능 추가)를 동시에 달성한다. 런타임에 동적으로 기능을 추가·제거할 수 있어 유연성이 높다.
한계는 데코레이터 체이닝이 깊어질수록 디버깅이 어렵고, 데코레이터가 너무 많으면 코드 흐름을 파악하기 어려워진다.
미래 방향으로는 ① 함수형 미들웨어(Middleware) 패턴이 데코레이터의 함수형 버전, ② 스프링 AOP가 데코레이터를 선언적으로 적용하는 현대적 방식으로 발전하고 있다.
- 📢 섹션 요약 비유: 양파(데코레이터 체인)는 겹겹이 벗길수록 구조가 복잡하지만, 각 겹이 독립적인 역할을 하여 전체를 구성한다.
📌 관련 개념 맵
[상속 기반 기능 확장 한계] → [데코레이터 패턴(구성+위임)] → [Java IO Stream] → [스프링 AOP] → [함수형 미들웨어]
| 개념 | 연결 포인트 |
|---|---|
| 프록시 패턴 | 구조 동일하나 목적이 접근 제어 |
| OCP (개방-폐쇄 원칙) | 데코레이터로 달성하는 핵심 원칙 |
| 스프링 AOP | 데코레이터 패턴의 프레임워크 구현 |
| Java IO | 데코레이터 패턴의 교과서적 구현 사례 |
📈 관련 키워드 및 발전 흐름도
[상속 서브클래스 폭발] → [GoF 데코레이터 패턴(1994)] → [Java IO 구현] → [스프링 AOP 선언적 데코레이터] → [함수형 미들웨어 패턴]
👶 어린이를 위한 3줄 비유 설명
- 데코레이터는 아이스크림에 토핑을 올리는 것처럼, 원래 아이스크림을 바꾸지 않고 기능을 추가해요.
- 초콜릿·견과류·크림을 순서대로 올릴 수 있고, 어떤 조합이든 자유롭게 만들 수 있어요.
- Java IO Stream이 바로 이 패턴을 사용해요!