디자인 패턴 (Design Pattern)

[구조 패턴] 데코레이터 패턴(Decorator pattern, 장식자)

계란💕 2023. 3. 12. 17:30

 

  • 데코레이터 패턴을 이용하면 타깃 객체를 감싸 어떤 로직을 런타임에 동적으로 부여할 수 있다. 
  • 데코레이터 끼리도 감쌀 수 있어서 사실상 타깃 객체를 장식하는 경우의 수는 이론적으로 무한하다. 
  • 데코레이터 패턴은 상속 보다 유연하지만. 애플리케이션 실행하기 전엔,ㄴ 객체 타입과  동작방식을 알 수 없어서 더 복잡하다. 
  • 거의 모든 언어와 UI, 플랫폼, 백엔드 할 것없이 두루 쓰인다. 

의도

  • 데코레이터 패턴은 객체들을 새로운 행동들을 포함한 특수 래퍼 객체들 내에 넣어서 위 행동들을 해당 객체들에 연결시키는 구조적 디자인 패턴이다. 

데코레이터 예시

 

  • Order 인터페이스
    • 가격, 피자 이름을 조회 가능
<hide/>
public interface Order {
    public double getPrice();
    public String getLabel();
}

 

 

  • Pizza implements Order
    • 주문을 구현한 클래스 Pizza
<hide/>
public class Pizza implements Order {

    private String label;
    private double price;

    public Pizza(String label, double price) {
        this.label = label;
        this.price = price;
    }

    public double getPrice() {
        return this.price;
    }
    
    public String getLabel() {
        return this.label;
    }
}

 

 

  • abstract class Extra implements Order
    • 피자 토핑 추가할 수 있는 추상 클래스 Extra
<hide/>
public abstract class Extra implements Order{
    
    protected Order order;
    protected String label;
    protected double price;
    
    public Extra(String label, double price, Order order) {
        this.order = order;
        this.label = label;
        this.price = price;
    }

    public abstract double getPrice();  // 단가는 민감한 문제이므로 실제 구현부에 위임한다.

    public String getLabel() {
        return order.getLabel() + ", " + this.label;
    }
}

 

 

  • RegularExtra extends Extra
    • 토핑을 추가하는 데코레이터
<hide/>
public abstract class Extra implements Order{
    
    protected Order order;
    protected String label;
    protected double price;
    
    public Extra(String label, double price, Order order) {
        this.order = order;
        this.label = label;
        this.price = price;
    }

    public abstract double getPrice();  // 단가는 민감한 문제이므로 실제 구현부에 위임한다.

    public String getLabel() {
        return order.getLabel() + ", " + this.label;
    }
}

 

 

  • NoCostExtra extends Extra
    • 무료 토핑을 추가하는 장식자
<hide/>
public class NoCostExtra extends Extra {
    
    public NoCostExtra(String label, double price, Order order) {
        super(label, price, order);
    }

    public double getPrice() {
        return order.getPrice();
    }
    
}

 

 

  • DoubleExtra extends Extra
    • 더블 토핑을 추가하는 장식자
<hide/>
public class DoubleExtra extends Extra {
    
    public DoubleExtra(String label, double price, Order order) {
        super(label, price, order);
    }
    
    public double getPrice() {
        return (this.price * 2) + order.getPrice();
    }
    
    public String getLabel() {
        return order.getLabel() + ", 더블 " + this.label;
    }
}

 

 


문제점

  •  예를 들어, 알림 라이브러리(중요 이벤트에 대해 다른 프로그램들이 사용자들에게 알리는)를 만드는 중이다. 
  • Notifier(알림자)  클래스에는 몇 개의 필드, 생성자,  send() 메서드(클라이언트로부터 메시지를 받은 다음, 생성자를 통해 알림자에게 전달된 이메일 목록으로 보낼 수 있다.)만 있다. 
  • 클라이언트 역할을 한 타사 앱은 알림자 객체를 한 번 생성하고 설정한 다음에 중요한 일이 발생할 때마다 사용하게 되어 있다. 

 

  • 다음과 같이 라이브러리 사용자들은 중요 정도에 따라 받고 싶은 알림의 종류가 다르다. 

 

 

 

  • 만약 여러 종류의 알림을 한 번에 받고 싶다면?
    • 다음과 같이 여러 알림 클래스를 합성한 특수 자식 클래스를 만든다. 
    • 그러나, 클래스의 개수가 지나치게 많다.

 


해결책

  • 상속에 관한 주의 사항
    • 상속은 정적이다. 
      • 런타임 때 기존 객체의 행동을 변경할 수 없다. 객체를 다른 자식 클래스에서 생성된 다른 객체로만 바꿀 수 있다. 
    • 자식 클래스는 하나의 부모 클래스만 상속할 수 있다. 
    • 해결 방법: 
      • 상속 대신 집합 관계 또는 합성을 사용하는 것이다. 
        • 집합 관계: 한 객체가 다른 객체에 대한 참조를 갖고 일부 작업을 위임한다. 
        • 상속: 객체 자체가 부모 클래스에서 행동을 상속한 후, 해당 작업을 수행할 수 있다. 

 

 

출처 - 「Java EE 디자인패턴 - 무라트 예네르, 알렉스 사돔 (길벗)」

출처 - https://refactoring.guru/ko/design-patterns/decorator

 

데코레이터 패턴

/ 디자인 패턴들 / 구조 패턴 데코레이터 패턴 다음 이름으로도 불립니다: 래퍼(Wrapper), Decorator 의도 데코레이터는 객체들을 새로운 행동들을 포함한 특수 래퍼 객체들 내에 넣어서 위 행동들을

refactoring.guru