데코레이터 패턴이란?

 

Decorate = 꾸미다. 장식하다.

 

1) 어려운 설명

객체에 동적으로 추가적인 기능들을 가진 객체를 덧붙여 꾸며주는 패턴.

심지어 동일한 클래스의 다른 객체에는 영향을 주지 않고 특정 개별 객체에만 기능을 추가해 줄 수도 있음.

 

마치 빌더패턴 기능이 있는 어댑터(wrapper)패턴과 유사한 느낌인데 어댑터는 기능 변환 없이 연결만 잘 되게 해주는 느낌이라면 데코레이터는 추가기능을 빌더패턴 느낌으로 wrapper로 감싸는 느낌이다.

 

2) 쉬운 설명

커피를 판매한다고 해보자.

에스프레소 클래스를 만들고 멤버함수로 getCost()와 gerIngredient()가 있다고 해보자.

getCost()는 1000을, gerIngredient()는 "coffee"를 return한다고 하자.

 ingredient : 재료

 

근데 만약 우유를 넣은 커피를 출시한다면?

그럼 밀크커피 클래스를 또 새로 만들고 가격은 1500을, 재료는 커피와 우유를 return하게 만들면 된다.

 

또 그 다음으로 휘핑크림을 넣은 커피를 출시한다면?

게다가 기존에 있던 재료까지 포함해서 커피 + 우유 + 휘핑크림 메뉴까지 추가해야 한다면?

이럴때마다 클래스를 계속 만들어야하고 재료가 수십종류만 된다해도 가능한 조합이 수억가지는 나올 수 있고 

수억개의 클래스를 만들 수는 없다.

 

그래서 에스프레소라는 기본 커피만 두고 거기에 다른 재료를 추가해주는 객체를 씌워서 데코레이트 해주는 개념이다.

예를들어 커피에 우유와 휘핑크림을 추가했다면 객체 생성시

CoffeeComponent* pComponent = 
	new MilkDecorator(new WhipingCreamDecorator(new CoffeeDecorator(new Coffee())));

위처럼 커피에 데코레이터들을 씌워주는 방식이다.

그렇다보니 Coffee 클래스를 변경할 필요도 없고 Coffee클래스의 여러 객체중 하나의 객체만 꾸며줄 수 있게 된다.

 

기능확장이 필요할때 기존 코드를 수정할 필요가 없으며 완전히 새로운 Sub Class를 만들 필요도 없는 유연한 방법이지만 데코레이터가 증가할수록 작은 규모의 객체들이 너무 많이 생기게 될 수 있다.

생성패턴의 빌더패턴을 채용한다면 기존에 만들어져있던 빌더 클래스는 계속 수정해나가야 하지만 새로운 클래스를 계속 만들어나갈 필요는 없으니 상황에 따라 비교선택해볼 수 있지 않을까 싶다.

 

 

 

데코레이터 패턴의 구조

 

커피와 동등선상에 있는 데코레이터를 하나 만들어서 커피와 데코레이터가 함께 Component를 상속한다.

ConcreteComponent자리에 기존의 원본 클래스가 들어간다.

 

 

위의 커피 예시를 구조화 하면 아래와 같다.

 

 

 

구현 코드 예시

 

1) Component 클래스

인터페이스 구현하는 추상클래스 부분

class CoffeeComponent
{
public:
    virtual int GetCost() = 0;
    virtual string GetIngredients() = 0;
};

 

2) ConcreteComponent 클래스

원본 클래스인 Coffee가 들어가는 부분

class Coffee : public CoffeeComponent
{
public:
    int GetCost() { return 1000; }
    string GetIngredients() { return "커피"; }
};

 

3) Decorator 클래스

원본클래스인 Coffee클래스와 동일선상의 클래스이며 Decorator들의 상위클래스

class CoffeeDecorator : public CoffeeComponent
{
private:
    CoffeeComponent* pComponent;

public:
    CoffeeDecorator(CoffeeComponent* c) : pComponent(c) {}
    ~CoffeeDecorator() { delete pComponent; }

    int GetCost() { return pComponent->GetCost(); }
    string GetIngredients() { return pComponent->GetIngredients(); }
};

 

4) MilkDecorator 클래스

상위 클래스인 CoffeeDecorator의 함수를 호출 후 거기에 추가작업을 수행해서 return하는 것이 핵심

CoffeeDecorator::GetCost()와 CoffeeDecorator::GetIngredients()를 주목

class MilkDecorator : public CoffeeDecorator
{
public:
    MilkDecorator(CoffeeComponent* d) : CoffeeDecorator(d) {}

public:
    int GetCost() { return 200 + CoffeeDecorator::GetCost(); }
    string GetIngredients() { return "우유 + " + CoffeeDecorator::GetIngredients(); }
};

 

5) WhipCreamDecorator 클래스

위의 Milk 데코레이터와 같은 역할

class WhipCreamDecorator : public CoffeeDecorator
{
public:
    WhipCreamDecorator(CoffeeComponent* d) : CoffeeDecorator(d) {}

public:
    int GetCost() { return 500 + CoffeeDecorator::GetCost(); }
    string GetIngredients() { return "휘핑크림 + " + CoffeeDecorator::GetIngredients(); }
};

 

6) main 함수

커피를 커피데코, 휘핑데코, 밀크데코로 감싸서 커피 + 휘핑크림 + 우유가 들어간 커피객체로 만듬

만약 커피 + 우유만 만들고 싶다면 아래에서 휘핑크림데코로 감싸는 부분만 빼면 되는 것

int main()
{
    CoffeeComponent* pComponent = new MilkDecorator(new WhipCreamDecorator(new CoffeeDecorator(new Coffee())));
    cout << "비용 : " << pComponent->GetCost() << endl;
    cout << "재료 : " << pComponent->GetIngredients() << endl;

    return 0;
}

 

7) 실행결과

 

 

 

 

 

 

※ 참고 문헌

https://chachayelmo.github.io/designpattern/designpattern-decorator/

https://copynull.tistory.com/136

https://d-yong.tistory.com/40

https://vallista.tistory.com/entry/6-Decorator-Pattern-in-C-%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0-%ED%8C%A8%ED%84%B4-C