데코레이터 패턴

임종혁·2024년 4월 21일

데코레이터란 장식하다 꾸미다라는 뜻이 가지고 있다.
그럼 무엇을 꾸민다는 것일까?

우선 데코레이터 패턴을 알아보기 앞서 헤드퍼스트 디자인패턴 책에서 나온것 처럼 커피로 예제로 들것이다.

우선 음료가 있다. 이 음료는 가격 이랑 설명을 알 수 있다. 그리고 음료에 첨가할 첨가물 이 있다 이 첨가물도 가격이랑 설명을 알 수 있다.

우선 음료를 구현하는 것은 espresso 로 할 것이고 첨가물은 mocha 로 할 것이다.

다음과 같이 코드로 나타내면

public interface Beverage {

     String getDescription();

      double cost();
}
public interface CondimentDecorator {
      String getDescription();


     double cost();
}
public class Expresso implements Beverage{

    String description = "에스프레소";
    double cost = 1.99;

    public CondimentDecorator condimentDecorator;

    public Expresso(CondimentDecorator condimentDecorator){
        this.condimentDecorator = condimentDecorator;
    }

    @Override
    public String getDescription() {
        return this.description + condimentDecorator.getDescription();
    }

    @Override
    public double cost() {
        return this.cost + condimentDecorator.cost();
    }
}
public class Mocha implements CondimentDecorator{

    String description = "에스프레소";
    double cost =  0.20;
    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public double cost() {
        return cost;
    }
}

실행

public class StarbuzzCoffe {

    public static void main(String[] args){
        CondimentDecorator mocha = new Mocha();
        Beverage expresso =new Expresso(mocha);

        System.out.println(expresso.getDescription());
        System.out.println(expresso.cost());
    }
}

다음과 같이 코드로 나타내면 나름 전략 패턴으로 캡슐화도 잘 시켜주었고 의존성 주입으로 통해 ioc 도 꽤 만족 시키는 코드인 것 같다.


다음과 같이 결과도 만족스럽게 잘 뜬다.

허나 여기서 하나 생각해야 할 것이 있다.

만약 옵션이 더욱 생겨 바닐라 샷 , 헤이즐럿 샷이 생겼다.

그럼 옵션을 모카, 바닐라샷, 헤이즐럿 샷으로 할 수 도 있고 (모카, 바닐라샷), (모카, 헤이즐럿샷), (바닐라샷, 헤이즐럿 샷) 일수도 있고 (모카, 바닐라샷, 헤이즐럿 샷) 일 수도 있다. 즉 옵션이 추가 될때 마다 구현 해야할 클래스가 조합으로 늘어난다는 문제가 있다.

그럼 이를 어떻게 해결 할 수 있을 것인가?

이것이 바로 데코레이터 패턴이다.
데코레이터 패턴 말그대로 꾸며주는 것이다.

즉 객체에 추가 요소를 동적으로 더할 수 있으며 훨씬 유연하게 기능을 확장할 수 있는 패턴이다.

여기서 동적이란 컴파일 시점 이 아닌 런타임 시점에서 기능을 추가할 수 있다는 뜻이며 즉 컴파일 시점인 클래스를 제작하는 것이 아닌 런타임에서 기능을 추가하는 것이다.

그럼 어떻게 이것이 가능 한 것일까?

책 그림처럼 바로 객체를 계속 감싸는 것이다.

코드로 보면

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

    public String getDescription(){
        return  description;
    }

    public abstract double cost();
}
public abstract class CondimentDecorator extends Beverage{

    Beverage beverage;

    public abstract String getDescription();

    @Override
    public double cost() {
        return 0;
    }
}

여기서 주목해야 할 점은 첨가물 즉 CondimentDecorator 안에 Beverage 자기 자신이 있는 것이다.
즉 target 을 불러와서 기능을 추가해 주면 형이 변하지도 않고 기능을 추가 할 수있다.

public class Espresso extends Beverage{


    public Espresso(){
        description = "에스프로소";
    }

    @Override
    public double cost() {
        return 1.99;
    }
}

public class Mocha extends CondimentDecorator{

    public Mocha(Beverage beverage){
        this.beverage   = beverage;
    }
    public String getDescription(){
        return beverage.getDescription() + "모카";
    }

    @Override
    public double cost() {
        return beverage.cost()  + .20;
    }
}
public class StarbuzzCoffe {

    public static void main(String args[]){
        Beverage beverage = new Espresso();

        beverage = new Mocha(beverage);

        System.out.println(beverage.getDescription());
        System.out.println(beverage.cost());
    }
}
 beverage = new Mocha(beverage);

즉 다음과 같이 객체에 추가하고 싶은 기능을 같은 타입으로 확장 시킨 후 해당 클래스에서 기능을 추가해 주면 된다
이러면 만약 바닐라샷, 헤이즐 럿 샷이 생겼다 해도

바닐라샷(모카(에스프레소)), 헤이즐럿 샷(모카(에스프레소)) 처럼 컴파일시 동적으로 기능을 추가 할 수 있는 것이다.

참고로 이 형태를 프록시라 부르기로 했다.
여기서 추가적인 것은 다이나믹 프록시를 보면 된다.

0개의 댓글