[JAVA] 62. 디자인 패턴 - 데코레이터(장식 패턴)

윤설안's avatar
Jul 22, 2025
[JAVA] 62. 디자인 패턴 - 데코레이터(장식 패턴)

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문으로 처리하면 코드가 복잡해지고 유지보수도 어려워진다.
이럴 때 데코레이터 패턴을 적용하면 구조적으로 매우 깔끔하게 구현할 수 있다.
notion image
 

데코레이터 클래스 설계

모든 알림 클래스는 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(); } }
  • EmailNotifierNotifier 인터페이스를 구현하는 클래스입니다. 이로 인해 다른 알림 클래스들과 동일한 형태로 취급할 수 있습니다.
  • 클래스 내부에는 Notifier 타입의 필드 notifier가 있습니다. 이 필드는 다른 알림 객체를 참조하여, 중첩 구조를 만들 수 있게 합니다.
  • 생성자 EmailNotifier(Notifier notifier)는 외부에서 다른 Notifier 구현체를 주입받아, 알림 기능을 동적으로 조합할 수 있게 해줍니다. 예를 들어 new EmailNotifier(new BasicNotifier())처럼 사용할 수 있습니다.
  • 기본 생성자도 제공되며, 단독으로 이메일 알림 기능만 사용할 경우 활용할 수 있습니다.
  • send() 메서드는 먼저 "이메일 알림"이라는 메시지를 출력한 뒤, 내부에 notifier가 존재할 경우 해당 객체의 send()도 호출합니다.
  • 이 방식은 자신의 알림 기능을 실행한 후, 다음 알림 기능에게 전달하는 연쇄 호출 구조를 형성합니다. 결과적으로 여러 알림 기능이 순차적으로 실행됩니다.
SmsNotifierEmailNotifier와 동일한 방식으로 구현하면 됩니다.

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

An's Blog