본 게시글은 '헤드퍼스트-디자인 패턴 (개정판)'을 기준으로 작성된 글입니다.
데코레이터 패턴이란?
객체의 추가 요소를 동작으로 더할 수 있다. 데코레이터를 사용하면, 서브클래스를 만들 때 보다 훨씬 유연하게 기능을 확장할 수 있다.
가정 - 카페
카페의 음료 주문 시스템을 구현하는 상황을 가정해보자.
가정 1
카페에는 아이스티도 있고, 카페모카도 있고, 아이스 아메리카노 등 여러가지 음료가 있다.
공통적으로 가지고 있는 '음료'라는 성질을 가지고 있는 클래스를 만들고, 이것을 상속받아 각 음료를 구현하였다.

가정 1의 문제
만약에 새로운 손님이 와서 '휘핑도 추가해주시고, 여기 샷도 추가해주세요' 와 같은 새로운 요구사항이 담긴 주문을 하게 된다면, 어떻게 될까?

다음 그림과 같이 매우 복잡한 결과가 나타날 것이다. 매번 새로운 요구사항이 들어올 때마다 맞는 클래스를 생성해주어야 한다.
해결 (1)
Berverage 에 Boolean 타입 변수를 추가해주어 해당 재료를 첨가하면, 각 재료들의 가격을 더한다.

(1)의 방법으로 해결했을 때의 문제점 - OCP위반
OCP란 ? : 확장에 열려 있고 변경에는 닫혀 있어야 한다.
문제점 :
- 차후에 메뉴가 새롭게 추가가 되면, 수퍼 클래스에 재료를 추가해주거나 수정해야하는 상황이 발생한다.
- 메뉴가 점점 다양해질수록 Beverage 클래스의 정의가 모호해진다.
- 예를 들어 Tea(차)를 팔게된다면, 휘핑크림을 첨가하는 (Cream)는 사용하지 않지만, 클래스 내부에는 필요없는 상태로 남아있다.
해결 - 데코레이터 패턴 사용


음료를 정의 해놓은 클래스를 정의하고, 각각의 재료가 음료를 감싸도록 한다.
code : Beverage.class
가장 상위의 슈퍼 클래스이다. 음료의 공통적인 성질을 따로 뺀 것으로 카페에서 판매하는 모든 음료는 이 클래스를 상속받아야 한다.
public abstract class Beverage {
String description = "no description";
public abstract int cost();
public String getDescription() {
return description;
}
}
code : CondimentDecorator.class
모든 첨가물들이 상속 받아야 하는 클래스로 Beverage클래스를 확장하며, 첨가물에게 getDescription 클래스를 새롭게 정의하도록 한다.(추상클래스)
public abstract class CondimentDecorator extends Beverage{
public abstract String getDescription();
}
code : Americano.class
public class Americano extends Beverage{
public Americano()
{
super();
description = "아메리카노";
}
@Override
public int cost(){
return 4000;
}
}
code : CaffeLatte
public class CaffeLatte extends Beverage{
public CaffeLatte()
{
super();
description = "카페라때";
}
@Override
public int cost()
{
return 5000;
}
}
code : Cream.class
public class Cream extends CondimentDecorator{
Beverage beverage;
public Cream(Beverage beverage){
super();
this.beverage = beverage;
}
@Override
public String getDescription()
{
return beverage.getDescription() + ", 크람";
}
@Override
public int cost() {
return beverage.cost() + 500;
}
}
code : Shot.class
public class Shot extends CondimentDecorator{
Beverage beverage;
public Shot(Beverage beverage) {
super();
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", 샷";
}
@Override
public int cost() {
return beverage.cost() + 400;
}
}
code : Customer.class
public class Customer {
public static void main(String[] args)
{
Beverage beverage = new Americano();
beverage = new Shot(beverage);
beverage = new Shot(beverage);
System.out.println("메뉴 : " + beverage.getDescription());
System.out.println("가격 : " + beverage.cost());
}
}
실행결과

호출 과정
- 가장 밖의 샷의 cost() 함수를 호출한다.
- 샷의 cost()는 샷의 cost()를 호출한다.
- 샷의 cost()는 아메리카노의 cost()를 호출한다
- 4000 + 400 + 400 이므로 4800원이 호출된다
정리
- 데코레이터의 슈퍼클래스는 자신이 장식하고 있는 객체의 슈퍼클래스와 같다.
- 한 객체를 여러 개의 데코레이터로 감쌀 수 있다.
- 데코레이터는 자신이 감싸고 있는 객체와 같은 슈퍼클래스를 가지고 있기 때문에, 원래 객체가 들어갈 자리에 데코레이터 객체를 넣어도 상관이 없다.
- 데코레이터는 자신이 장식하고 있는 객체에게 어떤 행동을 위임하는 일 말고도 추가작업을 수행할 수 있다.
'디자인패턴' 카테고리의 다른 글
디자인 패턴 - 팩토리 패턴 (0) | 2023.09.05 |
---|---|
[디자인 패턴] - 옵저버 패턴 (0) | 2023.08.31 |
[디자인패턴] - 전략 패턴 (0) | 2023.08.30 |