데코레이터 패턴이란? |
Decorate = 꾸미다. 장식하다.
1) 어려운 설명
객체에 동적으로 추가적인 기능들을 가진 객체를 덧붙여 꾸며주는 패턴.
심지어 동일한 클래스의 다른 객체에는 영향을 주지 않고 특정 개별 객체에만 기능을 추가해 줄 수도 있음.
마치 빌더패턴 기능이 있는 어댑터(wrapper)패턴과 유사한 느낌인데 어댑터는 기능 변환 없이 연결만 잘 되게 해주는 느낌이라면 데코레이터는 추가기능을 빌더패턴 느낌으로 wrapper로 감싸는 느낌이다.
2) 쉬운 설명
커피를 판매한다고 해보자.
에스프레소 클래스를 만들고 멤버함수로 getCost()와 getIngredient()가 있다고 해보자.
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/