컴퓨터과학/디자인패턴

[디자인패턴] 데코레이터 패턴 (Decorator Pattern)

윤호 2021. 10. 14. 19:30

목적

객체에 추가적인 책임을 동적으로 부여함

상속을 사용하지 않고 새로운 기능을 추가

데코레이터는 상속을 사용하지 않아도 유연하고 융통성 있는 기능 확장을 가능하게 함

서브클래스를 만들지 않고 기능을 유연하게 확장하는 방법 제공

요소

문제: 조금씩 다른 다양한 종류 -> 늘어날수록 확장 어려움

해결: 상속을 남용하지 않고 연관으로 필요한 기능 추가

결과: 확장성

정의

  • 여기서 상속은 기능을 물려받기 위해서가 아니라 형식을 맞추기 위해서임
  • Component: 각 구성요소는 직접 쓰일 수도 있고 데코레이터로 감싸져서 쓰일 수도 있음, 추상클래스 또는 인터페이스로 구현
  • ConcreteComponent: 새로운 행동을 동적으로 추가
  • Decorator: 자신이 장식할 구성요소(Component)와 같은 인터페이스 또는 추상 메소드 역할, Component의 기능 및 상태를 확장할 수 있음
  • ConcreteDecorator: 해당 객체가 장식하고 있는 것(Decorator가 감싸고 있는 Component 객체)을 위한 인스턴스 변수가 있음

 

데코레이터 패턴을 이용한 카페 음료

음료에 여러가지 옵션(휘핑, 샷 등등)을 추가하는 경우를 생각해보자

음료는 Component이고, 옵션들은 Decorator다.

Decorator들이 Component와 같이 Beverage를 상속받는 이유는 형식을 맞추기 위해서이다.

-> Component를 Decorator로 감싸도 근본이 훼손되지 않기 위해

Component인 DarkRoast을 Decorator들로 감쌈.

이렇게 감싸진 객체는 Beverage라는 근본은 잃지 않는다.

이 객체의 가격을 구할 경우 재귀호출을 통해 Component부터 Decorator까지의 가격들을 모두 더한 값을 반환하게 된다.

// Main 함수

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

        Beverage b = new DarkRoast();
        b = new Mocha(b); // 모카 추가
        b = new Mocha(b); // 모카 한 개 더 추가
        b = new Whip(b); // 휘핑 추가

        System.out.println(b.getDescription()
          +" $" + b.cost());
    }
}

 

// Component

public abstract class Beverage {
  String description = "제목 없음";
  
  public String getDescription() {
    return description;
  }

  public abstract double cost(); 

}


// Decorator

public abstract class CondimentDecorator extends Beverage {
  public abstract String getDescription(); 
}

 

 // Concrete Component
 
 public class Espresso extends Beverage {
  public Espresso() {
    description = "다크로스트"; 
  }

  public double cost() {
    return 1.99;
  }
}

 

// Concrete Decorator (CondimentDecorator는 Beverage를 상속받음)

public class Mocha extends CondimentDecorator {
  Beverage beverage;
  
  public Mocha(Beverage beverage) {
    this.beverage = beverage;
  }

  public String getDescription() {
    return beverage.getDescription() + ", 모카"; 
  }

  public double cost() {
    return beverage.cost() + .20;
  }
}