[디자인 패턴] - 데코레이터 패턴

본 게시글은 '헤드퍼스트-디자인 패턴 (개정판)'을 기준으로 작성된 글입니다.

데코레이터 패턴이란?

객체의 추가 요소를 동작으로 더할 수 있다. 데코레이터를 사용하면, 서브클래스를 만들 때 보다 훨씬 유연하게 기능을 확장할 수 있다.

 

가정 - 카페

카페의 음료 주문 시스템을 구현하는 상황을 가정해보자.

 

가정 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());
    }
}

실행결과

 

호출 과정

  1. 가장 밖의 샷의 cost() 함수를 호출한다. 
  2. 샷의 cost()는 샷의 cost()를 호출한다.
  3. 샷의 cost()는 아메리카노의 cost()를 호출한다
  4. 4000 + 400 + 400 이므로 4800원이 호출된다

정리

  • 데코레이터의 슈퍼클래스는 자신이 장식하고 있는 객체의 슈퍼클래스와 같다.
  • 한 객체를 여러 개의 데코레이터로 감쌀 수 있다.
  • 데코레이터는 자신이 감싸고 있는 객체와 같은 슈퍼클래스를 가지고 있기 때문에, 원래 객체가 들어갈 자리에 데코레이터 객체를 넣어도 상관이 없다.
  • 데코레이터는 자신이 장식하고 있는 객체에게 어떤 행동을 위임하는 일 말고도 추가작업을 수행할 수 있다.

'디자인패턴' 카테고리의 다른 글

디자인 패턴 - 팩토리 패턴  (0) 2023.09.05
[디자인 패턴] - 옵저버 패턴  (0) 2023.08.31
[디자인패턴] - 전략 패턴  (0) 2023.08.30