코드를 작성하다 보면 기존 객체의 핵심 동작은 유지하면서, 그 앞뒤로 부가 기능을 붙이고 싶을 때가 많습니다. 예를 들면 로그를 남기거나, 권한 체크를 하거나, 캐시를 붙이는 식입니다. 이때 상속으로 해결하려다 보면 구조가 무거워질 수 있는데, 데코레이터 패턴은 이런 상황에서 자주 좋은 대안이 됩니다.
이 글에서는 아래 내용을 정리합니다.
- 데코레이터 패턴이 무엇인지
- 왜 상속 대신 자주 언급되는지
- 어떤 상황에서 잘 맞는지
- 조합과 어떤 관계가 있는지
핵심은 데코레이터 패턴은 기존 객체를 감싸서 기능을 추가함으로써, 원래 객체를 바꾸지 않고도 동작을 확장할 수 있게 만드는 구조라는 점입니다.
데코레이터 패턴이란 무엇인가
데코레이터 패턴은 원래 객체와 같은 인터페이스를 가지는 래퍼 객체를 만들어, 기존 동작 앞뒤에 기능을 덧붙이는 구조입니다.
즉:
- 원래 객체를 바꾸지 않고
- 감싸는 객체를 통해
- 기능을 추가하는 방식입니다
왜 상속 대신 자주 쓰일까
상속으로 기능을 계속 붙이기 시작하면 조합 경우의 수가 늘어나고 구조가 경직되기 쉽습니다.
예를 들어:
- 로깅 있는 버전
- 캐시 있는 버전
- 로깅 + 캐시 있는 버전
식으로 클래스가 늘어나면 관리가 어렵습니다.
데코레이터는 이런 기능을 조합식으로 붙이기 쉽게 해줍니다.
어떤 상황에서 잘 맞을까
- 핵심 동작은 유지하고 싶을 때
- 부가 기능을 선택적으로 붙이고 싶을 때
- 여러 기능을 조합해서 감싸고 싶을 때
즉, “기본 기능 + 옵션 기능” 구조와 잘 맞습니다.
조합과는 어떤 관계가 있을까
데코레이터 패턴은 조합을 활용하는 대표적인 예입니다. 상속으로 계층을 늘리는 대신, 객체를 감싸는 방식으로 기능을 확장합니다.
그래서 상속보다 조합이라는 설계 감각과도 잘 연결됩니다.
자주 하는 오해
1. 데코레이터 패턴은 문법 장식이다
이름 때문에 오해할 수 있지만, 실제로는 구조 패턴입니다.
2. 기능 추가는 상속이 더 객체 지향적이다
변화가 잦은 부가 기능에는 데코레이터가 더 유연한 경우가 많습니다.
3. 데코레이터는 복잡한 프레임워크에만 필요하다
로깅, 권한 체크, 캐시처럼 일상적인 기능에서도 충분히 떠올릴 수 있습니다.
Simple Example
interface Notifier {
send(message: string): void;
}
class BasicNotifier implements Notifier {
send(message: string): void {
console.log(message);
}
}
class LoggingNotifier implements Notifier {
constructor(private wrapped: Notifier) {}
send(message: string): void {
console.log('log start');
this.wrapped.send(message);
}
}
class SmsNotifier implements Notifier {
constructor(private wrapped: Notifier) {}
send(message: string): void {
this.wrapped.send(message);
console.log(`Send SMS: ${message}`);
}
}
const notifier = new SmsNotifier(new LoggingNotifier(new BasicNotifier()));
notifier.send('Order completed');
The core behavior stays inside BasicNotifier, while logging and SMS delivery are added by wrapping it layer by layer.
FAQ
Q. 입문자는 어디서 데코레이터를 떠올리면 좋을까
핵심 로직은 그대로 두고 부가 기능만 앞뒤에 붙이고 싶을 때가 좋은 출발점입니다.
Q. 데코레이터와 전략 패턴은 같은가
같지 않습니다. 전략은 행동 교체, 데코레이터는 기능 덧붙이기에 더 가깝습니다.
Q. 데코레이터를 너무 많이 겹치면 안 복잡할까
그럴 수 있습니다. 그래서 기능 분리와 조합 기준을 적절히 잡는 것이 중요합니다.
Read Next
- 조합 중심 설계는 상속 vs 조합 가이드와 가장 잘 이어집니다.
- 행동 교체 구조는 전략 패턴 가이드와 비교해보면 좋습니다.
심사 대기 중에는 광고 대신 관련 가이드를 먼저 보여줍니다.
먼저 읽어볼 가이드
검색 유입이 많은 핵심 글부터 이어서 보세요.
- 미들웨어 트러블슈팅 가이드: Redis vs RabbitMQ vs Kafka 개발자를 위한 미들웨어 트러블슈팅 허브 글입니다. Redis, RabbitMQ, Kafka 중 어떤 증상부터 먼저 봐야 하는지와 어떤 문제 패턴이 각 시스템에 가까운지 정리합니다.
- Kubernetes CrashLoopBackOff: 먼저 볼 것들 startup failure, probe, config, resource limit 관점에서 CrashLoopBackOff를 어떻게 나눠서 봐야 하는지 정리한 가이드입니다.
- Kafka consumer lag가 계속 늘 때: 트러블슈팅 가이드 Kafka consumer lag가 계속 늘어날 때 무엇부터 봐야 하는지 정리합니다. poll 주기, 처리 속도, rebalance, consumer 설정까지 실전 기준으로 다룹니다.
- Kafka Rebalancing Too Often 가이드 Kafka consumer group에서 rebalance가 너무 자주 일어날 때 membership flapping, poll timing, protocol, assignment churn을 어떤 순서로 봐야 하는지 설명하는 실전 가이드입니다.
- Docker container가 계속 재시작될 때: 먼저 확인할 것들 exit code, command failure, environment mistake, health check 관점에서 Docker restart loop를 푸는 실전 가이드입니다.
심사 대기 중에는 광고 대신 관련 가이드를 먼저 보여줍니다.