본문 바로가기
SWE/C++ OOP

[디자인패턴] 데코레이터 패턴 | 악세사리 가격 계산 예시 코드

by S나라라2 2021. 8. 23.
반응형

Decorator Pattern 데코레이터 패턴

 

개념

데코레이터 패턴에서는 데코레이터가 객체를 겹겹이 감쌈(wrapping)으로써 객체에 추가적인 요건을 동적으로 첨가한다.

 

데코레이터 패턴은 이름처럼 감싸는, 꾸며주는 패턴이다. 

개념만 보고는 이해가 어려울 수 있으니, 액세서리를 예로 들어보겠다.

 

 

예시- 판도라 팔찌

판도라 팔찌는 링을 고르고 자기 스타일대로 참을 골라서 끼우는 걸로 유명하다.

 

고객이 팔찌와 참들을 자기 취향에 맞게 고르면, 그 팔찌의 가격을 계산하는 프로그램을 데코레이터 패턴을 사용해서 구현해보자.

1. 팔찌를 고른다.

2. 참들을 고른다.

예를 들어 고객이 은팔찌에 별과 하트참을 추가한다면 객체는 이런 모습을 가질 것이다.

처음엔 silverbracelet을 선택하고 금액을 산출한다. 그리고 별모양의 참을 추가하고 그에 금액을 더한다.

그리고 하트 모양의 참을 추가하여 금액을 더한다. 

이렇게 객체를 데코레이터가 겹겹이 감싸면서 동적으로 추가하는 것을 데코레이터 패턴이라고 한다.

 

이 개념을 코드로 구현하기 위해 각각의 참들은 모두 팔찌객체를 상속받으면서, 팔찌 객체를 멤버 변수로 가지고 있다. 

이 부분(팔찌를 상속받는 것과 동시에 멤버 변수로도 가지고 있는 것)이 데코레이터 패턴의 키포인트이다.

객체 구성(인스턴스 변수로 다른 객체를 저장하는 방식)을 이용하고 있기 때문에, 참들을 다양하게 계속 추가하여도 유연성을 잃지 않을 수 있다.

여기서 참을 데코레이터라고 일컫는다.

데코레이터 패턴 UML - 판도라 팔찌 예시

팔찌 객체가 있다. 

이 팔찌 객체를 상속받은 은팔지, 금팔찌, 가죽 팔찌가 있다. 

데코레이터는 팔찌 객체를 상속받았다.

그리고 별참, 하트참, 꽃참은 데코레이터를 상속받았기때문에 최종적으로는 팔찌객체를 상속받은 것이다.

또한 각각의 참들은 팔찌 객체를 멤버 변수로 가지고 있다.

 

코드 구조

팔찌와 데코레이터로 폴더를 나눠놨다.

 

팔찌 클래스

// bracelet.h
#ifndef BRACELET_H
#define BRACELET_H

#include <string>

class Bracelet
{
public:
	Bracelet();
	~Bracelet();

public:
	std::string m_description;

// 순수가상함수로 정의
// Bracelet을 상속받은 모든 클래스는 필수로 해당 함수들을 구현해야 함
public:
	virtual int cost()=0;

public:
	virtual std::string getDescription();

};

#endif


// bracelet.cpp
#include "bracelet.h"

Bracelet::Bracelet()
{
	m_description = "bracelet";
}

Bracelet::~Bracelet()
{

}

std::string Bracelet::getDescription()
{
	return m_description;
}

 

팔찌 객체를 상속 받은 금팔찌 클래스

// gold.h
#ifndef GOLD_H
#define GOLD_H

#include "bracelet.h"

#include <string>

class Gold : public Bracelet
{
public:
	Gold();
	~Gold();

public:
	virtual int cost();
};

#endif

// gold.cpp
#include "gold.h"

Gold::Gold()
{
	m_description = "Gold";
}

Gold::~Gold()
{
}

int Gold::cost()
{
	return 300;
}

 

은팔찌, 가죽팔찌 모두 금팔찌와 동일한 코드를 가지고 있으므로 생략한다.

 

데코레이터 첨가물 클래스

// condimentdecorator.h
#ifndef CONDIMENTDECORATOR_H
#define CONDIMENTDECORATOR_H

#include "../bracelet/bracelet.h"

#include <string>

class CondimentDecorator : public Bracelet
{
public:
	CondimentDecorator();
	~CondimentDecorator();

public:
	Bracelet* m_bracelet;

public:
	virtual std::string getDescription();
};
#endif

// condimentdecorator.cpp
#include "condimentdecorator.h"

CondimentDecorator::CondimentDecorator()
{
}

CondimentDecorator::~CondimentDecorator()
{
}

std::string CondimentDecorator::getDescription()
{
	return m_bracelet->getDescription() + m_description;
}

 

데코레이터 첨가물 객체를 상속받은 꽃 참 클래스

// flowercharm.h
#ifndef FLOWERCHARM_H
#define FLOWERCHARM_H

#include "condimentdecorator.h"

class FlowerCharm : public CondimentDecorator
{
public:
	FlowerCharm(Bracelet* bracelet);
	~FlowerCharm();

public:
	virtual int cost();
};

#endif

// flowercharm.cpp
#include "flowercharm.h"

FlowerCharm::FlowerCharm(Bracelet* bracelet)
: CondimentDecorator()
{
	m_description = ", Flower Charm";
	m_bracelet = bracelet;
}

FlowerCharm::~FlowerCharm()
{
}
	
int FlowerCharm::cost()
{
	return m_bracelet->cost() + 30;
}

별참, 하트참도 꽃참과 같은 코드를 가지고 있다. 따라서 생략한다.

 

테스트 코드

// main.cpp 
#include <iostream>

#include "bracelet/bracelet.h"
#include "bracelet/silver.h"
#include "bracelet/gold.h"
#include "bracelet/leather.h"
#include "decorator/condimentdecorator.h"
#include "decorator/starcharm.h"
#include "decorator/heartcharm.h"
#include "decorator/flowercharm.h"

int main()
{
    std::cout << "[Decorator Pattern Example] \n";

	Bracelet* bracelet1 = new Silver();
	bracelet1 = new StarCharm(bracelet1);
	std::cout << bracelet1->getDescription() << " $" << bracelet1->cost()<<std::endl;

	Bracelet* bracelet2 = new Silver();
	bracelet2 = new HeartCharm(bracelet2);
	bracelet2 = new FlowerCharm(bracelet2);
	bracelet2 = new HeartCharm(bracelet2);
	std::cout << bracelet2->getDescription() << " $" << bracelet2->cost() << std::endl;
}

 

위 코드를 실행하면 아래와 같은 결과를 얻을 수 있다.

 

실행 결과

 

데코레이터의 특징

- 데코레이터의수퍼 클래스는 자신이 장식하고 있는 객체의 수퍼 클래스와 같다.

- 한 객체를 여러 개의 데코레이터로 감쌀 수 있다.

- 데코레이터는 자신이 감싸고 있는 객체와 같은 수퍼클래스를 가지고 있기 때문에 원래 객체 (싸여져 있는 객체)가 들어갈 자리에 데코레이터 객체를 집어넣어도 상관 없습니다. -> 위의 코드 예시에서 시도해보려면 null 예외조건 처리를 해야지 가능하다.

- 데코레이터는 자신이 장식하고 있는 객체에게 어떤 행동을 위임하는 것 외에 원하는 추가적인 작업을 수행할 수 있다.

- 객체는 언제든지 감쌀 수 있기 때문에 실행중에 필요한 데코레이터를 마음대로 적용할 수 있다.

 

 

반응형