데코레이터 패턴이란?

구조 패턴 중 하나
객체에 추가 요소를 동적으로 더할 수 있다
서브클래스를 만들 때보다 더 유연하게 기능을 확장할 수 있다

 

패턴 적용 전 코드

public interface Notifier {

    void send();
}

public class BasicNotifier implements Notifier {

    @Override
    public void send() {
        System.out.println("기본 알림");
    }
}

public class EmailNotifier implements Notifier {

    @Override
    public void send() {
        System.out.println("이메일 알림");
    }
}

알림 기능을 위한 인터페이스를 하나 정의해보자

  • BasicNotifier 클래스는 기본 알림 구현
  • EmailNotifier 클래스는 이메일 알림을 구현

 

public class Application {
    public static void main(String[] args) {
        Notifier notifier = new BasicNotifier();
        notifier.send();
        System.out.println("--end--");

        Notifier emailNotifier = new EmailNotifier(new BasicNotifier());
        emailNotifier.send();
        System.out.println("--end--");
    }
}

-------------------------------------------
기본 알림
--end--
이메일 알림
--end--

하지만 이메일 알림과 기본 알림을 동시에 해야하는 요구사항이 생겼을 때
어떻게 처리를 하는 것이 좋을까?

여기서 프록시 패턴을 생각할 수도 있지만 데코레이터 패턴을 적용할 것이다

 

패턴 적용 후

public interface Notifier {

    void send();
}

public class BasicNotifier implements Notifier {

    @Override
    public void send() {
        System.out.println("기본 알림");
    }
}

public class EmailNotifier implements Notifier {

    private Notifier notifier;
    
    public EmailNotifier(Notifier notifier) {
        this.notifier = notifier;
    }

    @Override
    public void send() {
        notifier.send();
        System.out.println("이메일 알림");
    }
}

// 문자 알림도 추가
public class SmsNotifier implements Notifier {

    private Notifier notifier;

    public SmsNotifier(Notifier notifier) {
        this.notifier = notifier;
    }

    @Override
    public void send() {
        notifier.send();
        System.out.println("문자 알림");
    }
}

 

메인메소드에 실행을 시켜보면

public class Application {
    public static void main(String[] args) {

        // 1. 기본 알림 -> 이메일 알림 -> 문자 알림
        Notifier allNotifier = new SmsNotifier(new EmailNotifier(new BasicNotifier()));
        allNotifier.send();
        System.out.println("--end--");

        // 2. 기본 알림 -> 문자 알림 -> 이메일 알림
        Notifier allNotifier2 = new EmailNotifier(new SmsNotifier(new BasicNotifier()));
        allNotifier2.send();
        System.out.println("--end--");
    }
}
----------------------------------------------
기본 알림
이메일 알림
문자 알림
--end--
기본 알림
문자 알림
이메일 알림
--end--

이렇게 요구사항에 맞게 알림을 다양하게 줄 수 있게 되었다

 

아까 전에 프록시 대신 데코레이터를 선택한 이유는?

프록시 패턴은 접근에 대해 제어를 목적으로 하고
데코레이터 패턴은 기존 기능에 다른 기능을 추가하는 목적이기 때문이다



프록시 패턴과 공통점과 차이점

공통점

  • 둘 다 interface 를 구현

차이점

  • 프록시 패턴은 패턴이 적용된 클래스들의 관계가 컴파일 시에 정해짐
  • 데코레이터 패턴은 런타임 시에 정해짐

 

데코레이터 패턴을 적용했지만 해결하지 못한 문제점

이메일 알림과, 문자 알림 이 두 알림 중에 하나만 사용하고 싶을 때는 문제가 생긴다

왜냐하면 생성자에서 인터페이스를 주입받고 있기 때문이다

이때 아주 손쉽게 해결을 할 수 있는데

public class EmailNotifier implements Notifier {

    private Notifier notifier;

    public EmailNotifier() {}

    public EmailNotifier(Notifier notifier) {
        this.notifier = notifier;
    }

    @Override
    public void send() {
        if (notifier != null) {
            notifier.send();
        }
        System.out.println("이메일 알림");
    }
}

이렇게 기본 생성자를 만들고 오버라이딩한 알림을 보내는 메소드에서

Notifier null 체크를 해서 손쉽게 이메일 알림만을 보낼 수 있게 되었다

 

장점

  1. 새로운 서브클래스를 만들지 않고 객체의 행동을 확장할 수 있다
  2. 런타임 시 기능을 추가하거나 제거할 수 있다
  3. 여러 데코레이터로 래핑하여 여러 행동을 합칠 수 있다
  4. 단일 책임 원칙 - 다양한 기능들의 다양한 변형들을 여러 개의 작은 클래스로 나눌 수 있다

 

단점

  1. 데코레이터 스택에서 특정 래퍼클래스를 제거하기 어렵다
  2. 순서에 의존하지 않게 데코레이터를 구현하기가 어렵다
  3. 계층들의 초기 설정 코드가 더러울 수 있다
복사했습니다!