- 데코레이터 패턴을 이용하면 타깃 객체를 감싸 어떤 로직을 런타임에 동적으로 부여할 수 있다.
- 데코레이터 끼리도 감쌀 수 있어서 사실상 타깃 객체를 장식하는 경우의 수는 이론적으로 무한하다.
- 데코레이터 패턴은 상속 보다 유연하지만. 애플리케이션 실행하기 전엔,ㄴ 객체 타입과 동작방식을 알 수 없어서 더 복잡하다.
- 거의 모든 언어와 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
'디자인 패턴 (Design Pattern)' 카테고리의 다른 글
[구조 패턴] 프록시 패턴(Proxy pattern) (0) | 2023.03.13 |
---|---|
[구조 패턴] 퍼사드 패턴(Facade pattern) (0) | 2023.03.13 |
[행동 패턴] 커맨드 패턴(Command Pattern, 명령 패턴) (0) | 2023.02.27 |
[행동 패턴] 템플릿 메서드(Template Method) (0) | 2023.02.27 |
[행동 패턴] 전략 패턴(Strategy Pattern) (0) | 2023.02.27 |