1. 개념
- 기능을 동적으로 확장할 수 있는 패턴
- 기존 코드를 수정하지 않고 새로운 기능을 추가할 수 있음 → OCP(Open-Closed Principle) 만족
- 여러 기능을 조합해서 사용할 수 있음 (예: 기본 알림 + 이메일 + 문자)
2. 상속 vs 컴포지션
구분 | 설명 |
상속 (Inheritance) | 한 가지만 확장 가능 (단일 상속) 확장보다는 추상화 목적에 가깝다 |
컴포지션 (Composition) | 여러 개의 기능을 조합 가능 기능을 동적으로 추가하거나 제거할 수 있음 |
하지만 컴포지션만 사용하면 기능 추가/삭제 시 코드가 계속 바뀌어야 하므로 OCP를 위배하게 됨.
➡️ 데코레이터 패턴을 사용하면 기존 코드를 건드리지 않고 기능을 확장할 수 있어 OCP를 만족시킴
3. 코드 예시
BasicNotifier b1 = new BasicNotifier();
b1.send(); // OK
EmailNotifier e1 = new EmailNotifier();
e1.emailSend(); // 이름이 다름 → 일관성 없음
BasicNotifier
는 내가 만든 클래스이고, EmailNotifier
는 팀원이 만든 클래스라고 가정해보자.
나는 send()
라는 메서드를 사용하고 있는데, 팀원은 emailSend()
라는 이름으로 구현했다.
이처럼 메서드 이름이 제각각이면, 사용하는 쪽에서는 혼란이 생길 수밖에 없다.
결국, 이름을 맞추기 위해 emailSend()
를 send()
로 변경해야 하고, 이건 이미 작업한 내용을 수정하게 되어 불필요한 유지보수 비용이 발생한다.해결 방법
이런 문제는 인터페이스를 사용하여 통일된 메서드명(
send
)을 강제하면 쉽게 해결할 수 있다.public interface Notifier {
void send();
}
인터페이스를 통해 모든 알림 클래스가
send()
메서드를 구현하도록 강제하면,- 어떤 구현체를 사용하든 메서드 호출 방식은 동일하고
- 팀원 간의 불필요한 조율 없이 개발 가능하다
동적 바인딩
public class App {
public static void main(String[] args) {
Notifier b1 = new BasicNotifier();
b1.send();
System.out.println("_end");
Notifier e1 = new EmailNotifier();
e1.send();
System.out.println("_end");
Notifier s1 = new SmsNotifier();
s1.send();
System.out.println("_end");
}
}
Notifier
인터페이스 타입으로 선언한 변수는, 어떤 알림 클래스든 참조 가능
- 실행 시에는 실제 인스턴스 타입의
send()
메서드가 호출됨
→ 동적 바인딩(Dynamic Binding) 을 통해 유연하게 동작
4. 데코레이터 패턴 적용
기존에는 각 알림 기능을 개별적으로 호출하거나 조합하려면 if문 또는 여러 클래스를 만들거나, 메서드를 반복 호출해야 했습니다.
하지만 데코레이터 패턴을 적용하면, 알림 기능을 유연하게 조합하고 확장 할 수 있습니다.
적용 예시
어떤 서비스에서 사용자가 알림 수단을 선택한 뒤, "전송" 버튼을 한 번만 클릭하면
기본 알림, 이메일 알림, 문자 알림 등 선택한 모든 방식으로 동시에 알림이 전송되도록 만들고 싶다고 해보자.
각 알림을 따로따로 호출하거나 if문으로 처리하면 코드가 복잡해지고 유지보수도 어려워진다.이럴 때 데코레이터 패턴을 적용하면 구조적으로 매우 깔끔하게 구현할 수 있다.

데코레이터 클래스 설계
모든 알림 클래스는
Notifier
인터페이스를 구현합니다.그 중
EmailNotifier
, SmsNotifier
는 데코레이터 역할을 합니다.
즉, 자기 자신의 알림 기능을 수행한 뒤, 내부에 포함된 다른 Notifier
에게 알림을 전달(delegate) 하는 구조입니다.public class EmailNotifier implements Notifier {
private Notifier notifier;
public EmailNotifier(Notifier notifier) {
this.notifier = notifier;
}
public EmailNotifier() {
}
public void send() {
System.out.println("이메일 알림");
if (notifier != null) notifier.send();
}
}
EmailNotifier
는Notifier
인터페이스를 구현하는 클래스입니다. 이로 인해 다른 알림 클래스들과 동일한 형태로 취급할 수 있습니다.
- 클래스 내부에는
Notifier
타입의 필드notifier
가 있습니다. 이 필드는 다른 알림 객체를 참조하여, 중첩 구조를 만들 수 있게 합니다.
- 생성자
EmailNotifier(Notifier notifier)
는 외부에서 다른Notifier
구현체를 주입받아, 알림 기능을 동적으로 조합할 수 있게 해줍니다. 예를 들어new EmailNotifier(new BasicNotifier())
처럼 사용할 수 있습니다.
- 기본 생성자도 제공되며, 단독으로 이메일 알림 기능만 사용할 경우 활용할 수 있습니다.
send()
메서드는 먼저"이메일 알림"
이라는 메시지를 출력한 뒤, 내부에notifier
가 존재할 경우 해당 객체의send()
도 호출합니다.
- 이 방식은 자신의 알림 기능을 실행한 후, 다음 알림 기능에게 전달하는 연쇄 호출 구조를 형성합니다. 결과적으로 여러 알림 기능이 순차적으로 실행됩니다.
SmsNotifier
도 EmailNotifier
와 동일한 방식으로 구현하면 됩니다.BasicNotifier는 변경하지 않는 것이 좋습니다.
BasicNotifier
는 데코레이터 연쇄 구조의 끝(종료점) 역할을 합니다.이 연쇄 구조에서 끝이 명확히 존재하는 것이 좋은 이유는 다음과 같습니다.
- 명확한 종료점이 있어야 무한 호출을 방지할 수 있습니다. 데코레이터가 계속 내부의
Notifier
를 호출하는 구조이므로, 종료점이 없으면 호출이 무한 반복될 위험이 있습니다.
- 체인의 끝에 위치하여 기본 알림 기능을 책임지므로, 역할이 명확합니다.
- 다른 데코레이터들은 기능을 확장하는 역할만 담당하고, 기본 기능은
BasicNotifier
가 수행하는 형태로 책임이 분리됩니다.
- 유지보수와 확장성 측면에서 기본 기능과 추가 기능을 구분하여 관리할 수 있습니다.
따라서
BasicNotifier
는 안정적으로 연쇄 구조를 마무리하는 역할을 하므로, 별도의 변경 없이 유지하는 것이 좋습니다.코드 예시
// 1. 이메일 알림
EmailNotifier n1 = new EmailNotifier();
n1.send();
System.out.println();
// 2. 기본 알림 + 이메일 알림
EmailNotifier n2 = new EmailNotifier(new BasicNotifier());
n2.send();
System.out.println();
// 3. 이메일 알림 + 문자 알림
EmailNotifier n3 = new EmailNotifier(new SmsNotifier());
n3.send();
System.out.println();
// 4. 문자알림
SmsNotifier n4 = new SmsNotifier();
n4.send();
System.out.println();
// 5. 문자 알림 + 이메일 알림 + 기본 알림
SmsNotifier n5 = new SmsNotifier(new EmailNotifier(new BasicNotifier()));
n5.send();
System.out.println();
// 6. 문자 알림 + 문자 알림 + 이메일 알림 + 기본 알림
SmsNotifier n6 = new SmsNotifier(new SmsNotifier(new EmailNotifier(new BasicNotifier())));
ClientNotification.send(n6);
📌 정리
- 데코레이터 패턴은 기능 추가 시 기존 클래스를 수정하지 않아도 되는 구조를 제공한다.
→ OCP (Open-Closed Principle) 만족
- 중첩 조합이 가능하므로, 다양한 알림 방식들을 유연하게 조합할 수 있다.
- 실무에서는 알림 외에도 로깅, 인증, 데이터 포맷 변환 등에서 자주 쓰인다.
Share article