JAVA/디자인 패턴

[Decorator Pattern] 데코레이터 패턴

dev_rosieposie 2023. 7. 4. 21:53

Goal 

  1. 데로레이터 패턴에 대해 알아보고 이해한다
  2. 샘플코드를 통한 데코레이터 패턴을 이해한다

 

데코레이터 패턴 (Decorator Parttern) 

객체에 추가 요소를 동적으로 더할 수 있는 패턴. 

 

 

다이어그램

 

 

데코레이터 패턴을 사용하는 이유

상속을 사용할 경우 클래스가 많아지거나 일부 서브클래스에는 적합한 기능을 추가해야하는 문제가 있지만, 데코레이터 패턴을 사용하면 보다 훨씬 유연하게 기능을 확장할 수 있기 때문이다.

 

주문 시스템에서 생각해보기

상황 : 상속을 써서 음료 가격과 첨과물(샷, 시럽, 우유, 휘핑크림 등) 가격을 합해서 총 가격을 산출하는 방법은 좋은 방법이 아님

요구 사항 : 다크로스트에 모카, 휘핑크림을 추가한 음료

해결 방안 : 데코레이터 패턴을 사용해서 가격을 계산해보자

 

1. DarkRoast 객체에서 시작한다

  • DarkRoast는 Beverage로부터 상속받으므로 음료의 가격을 계산하는 메소드를 가지고 있다

2. Mocha 객체로 장식한다

  • 고객이 모카를 주문했으니 Mocha를 만들고 그 객체로 DarkRoast객체를 감싼다
  • Mocha 객체는 데코레이터이다. 객체의 형식은 객체가 장식하고 있는 객체를 반영하는데, 이 경우에는 Beverage가 된다.
  • 따라서 Mocha에도 cost() 메소드 - 음료 가격 계산 메소드가 있꼬, Mocha가 감싸고 있는것도 Beverage 객체로 간주 할 수 있다 => 다형성

3. Whip 객체로 장식한다

  • Whip도 추가 했으니 Whip 객체를 만들고 Mocha를 감싼다
  • 역시나 DarkRoast의 형식을 반영하여 cost() 메소드를 가지고 있다

4. cost() 메소드를 호출한다. 이때 첨가물의 가격을 계산하는 일은 해당 객체에 위임한다

  • 가격을 구할 때는 가장 바깥쪽에 있는 데코레이터인 Whip의 cost()를 호출하면된다. 그러면 Whip은 그 객체가 장식하고 있는 객체에게 계산을 위임한다. 가격이 구해지고 나면, 계산된 가격에 휘핑크림의 가격을 더한 다음 그 결과값을 리턴한다
  • Whip의 cost() 호출 => Whip은 Mocha의 cost() 호출 => Mocha는 DarkRoast의 cost() 호출=> DarkRoast는 99센트 리턴=> Mocha는 99센트에 + 20센트 더해서 1.19달러 리턴 => Whip은 Mocha로부터 받은 가격에 10센트를 더해 1.29달러 리턴

 

데코레이터 패턴 특징

  • 데코레이터의 슈퍼클래스는 자신이 장식하고 있는 객체의 슈퍼클래스와 같다
  • 한 객체를 여러개의 데코레이터로 감쌀 수 있다
  • 데코레이터는 자신이 감싸고 있는 객체와 같은 슈퍼클래스를 가지고 있기에 원래 객체(싸여 있는 객체)가 들어갈 자리에 데코레이터 객체를 넣어도 된다
  • 데코레이터는 자신이 장식하고 있는 객체에게 어떤 행동을 위임하는 일 말고도 추가 작업을 수행할 수 있다
  • 객체는 언제든지 감쌀 수 있으므로 실행 중에 필요한 데코레이터를 마음대로 적용할 수 있다

 

데코레이터 패턴을 활용하여 커피 주문 시스템 코드 만들기 

 

음료 클래스 

public abstract class Beverage {
    String description = "제목 없음";

    public String getDescription(){
        return description;
    }
    public abstract double cost();
}

getDescription은 이미 구현되어 있지만 cost()는 서브클래스에서 구현해야함

첨가물 클래스 

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

 

각 데코레이터가 감쌀 음료를 나타내는 Beverage객체를 여기서 지정하고, 음료를 지정할 때는 데코레이터에서 어떤 음료든 감쌀 수 있도록 Beverage 슈퍼클래스 유형을 사용한다

또한 첨가물 데코레이터에 getDescription() 메소드를 새로 구현하도록 만들도록 추상메소드로 구현한다.

음료 코드 구현하기

public class Espresso extends Beverage{
    public Espresso() {
        description = "에스프레소";
    }
    @Override
    public double cost() {
        return 1.99;
    }
}

클래스 생성자 부분에서 description이라는 변수값을 설정하며, 이 인스턴스 변수는 Bevarage로부터 상속받음

이 클래스 첨가물 가격만 리턴한다

public class HouseBlend extends Beverage{
    public HouseBlend() {
        description = "하우스 블렌드 커피";
    }
    @Override
    public double cost() {
        return .89;
    }
}

 

첨가물 코드 구현하기

public class Mocha extends CondimentDecorator{
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }
    @Override
    public String getDescription() {
        return beverage.getDescription() +", 모카";
    }
    @Override
    public double cost() {
        return beverage.cost()+.20;
    }
}
  • 모카 인스턴스에는 Beverage의 레퍼런스가 들어있고 다음과 같이 두가지가 필요하다
    1. 감싸고자 하는 음료를 저장하는 인스턴스 변수
    2. 인스턴스 변수를 감싸고자 하는 객체로 설정하는 생성자
  • 설명에는 첨가물 아이템을 추가한다. 먼저 장식하고 있는 객체에 작업을 위힘하고 다음 결과에 모카를 더한 값을 리턴한다
  • 음료 가격에 모카를 추가한 가격을 계산해야한다. 우선 장식하고 있는 객체에 가격을 구하는 작업을 위임해서 음료값을 구하고 모카 값을 더하여 그 합을 리턴한다

 

커피 주문 시스템 코드 테스트

public class StarbucksCoffee {

    public static void main(String[] args){

        Beverage beverage = new Espresso();
        System.out.println(beverage.getDescription()+" $"+beverage.cost());

        Beverage beverage2 = new HouseBlend();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println(beverage2.getDescription()+" $"+beverage2.cost());
    }
}

결과

 

에스프레소 $1.99
하우스 블렌드 커피, 모카, 모카, 휘핑크림 $1.49