C++ Decorator 패턴
객체에 동적으로 새로운 책임을 추가합니다. 서브클래싱 대신 래핑을 통해 기능을 조합하며, 커피 주문처럼 여러 데코레이터를 중첩 적용할 수 있습니다.
#include <iostream>
#include <memory>
#include <string>
#include <sstream>
#include <iomanip>
// ──────────────────────────────────────────────
// 컴포넌트 인터페이스: 모든 음료의 기본
// ──────────────────────────────────────────────
class Beverage {
public:
virtual ~Beverage() = default;
virtual std::string description() const = 0;
virtual double cost() const = 0;
// 가격 포맷 헬퍼
std::string formattedCost() const {
std::ostringstream oss;
oss << std::fixed << std::setprecision(0) << cost() << "원";
return oss.str();
}
};
// ──────────────────────────────────────────────
// 구체 컴포넌트: 기본 음료 종류
// ──────────────────────────────────────────────
class Espresso : public Beverage {
public:
std::string description() const override { return "에스프레소"; }
double cost() const override { return 2000.0; }
};
class HouseBlend : public Beverage {
public:
std::string description() const override { return "하우스 블렌드 커피"; }
double cost() const override { return 1500.0; }
};
class DarkRoast : public Beverage {
public:
std::string description() const override { return "다크 로스트"; }
double cost() const override { return 1800.0; }
};
// ──────────────────────────────────────────────
// 추상 데코레이터: Beverage를 감싸는 기반 클래스
// ──────────────────────────────────────────────
class CondimentDecorator : public Beverage {
public:
explicit CondimentDecorator(std::unique_ptr<Beverage> beverage)
: wrapped_(std::move(beverage)) {}
protected:
// 감싸진 음료에 위임
const Beverage& wrapped() const { return *wrapped_; }
private:
std::unique_ptr<Beverage> wrapped_;
};
// ──────────────────────────────────────────────
// 구체 데코레이터: 첨가물 (스택처럼 중첩 가능)
// ──────────────────────────────────────────────
class Milk : public CondimentDecorator {
public:
using CondimentDecorator::CondimentDecorator;
std::string description() const override {
return wrapped().description() + ", 우유";
}
double cost() const override { return wrapped().cost() + 300.0; }
};
class Mocha : public CondimentDecorator {
public:
using CondimentDecorator::CondimentDecorator;
std::string description() const override {
return wrapped().description() + ", 모카";
}
double cost() const override { return wrapped().cost() + 400.0; }
};
class Whip : public CondimentDecorator {
public:
using CondimentDecorator::CondimentDecorator;
std::string description() const override {
return wrapped().description() + ", 휘핑크림";
}
double cost() const override { return wrapped().cost() + 500.0; }
};
class SoyMilk : public CondimentDecorator {
public:
using CondimentDecorator::CondimentDecorator;
std::string description() const override {
return wrapped().description() + ", 두유";
}
double cost() const override { return wrapped().cost() + 350.0; }
};
// ──────────────────────────────────────────────
// 주문 출력 헬퍼
// ──────────────────────────────────────────────
void printOrder(const Beverage& beverage) {
std::cout << " 주문: " << beverage.description() << "\n"
<< " 가격: " << beverage.formattedCost() << "\n";
}
// ──────────────────────────────────────────────
// 사용 예시
// ──────────────────────────────────────────────
int main() {
std::cout << "=== 커피숍 주문 시스템 (Decorator 패턴) ===\n\n";
// 주문 1: 에스프레소 단품
auto order1 = std::make_unique<Espresso>();
std::cout << "[주문 1] 에스프레소 단품\n";
printOrder(*order1);
// 주문 2: 다크 로스트 + 모카 두 번 + 휘핑크림
// 데코레이터를 체인처럼 감싸서 조합
std::unique_ptr<Beverage> order2 = std::make_unique<DarkRoast>();
order2 = std::make_unique<Mocha>(std::move(order2));
order2 = std::make_unique<Mocha>(std::move(order2)); // 모카 2번 추가
order2 = std::make_unique<Whip>(std::move(order2));
std::cout << "\n[주문 2] 다크 로스트 + 모카×2 + 휘핑크림\n";
printOrder(*order2);
// 주문 3: 하우스 블렌드 + 두유 + 모카 + 휘핑크림
std::unique_ptr<Beverage> order3 = std::make_unique<HouseBlend>();
order3 = std::make_unique<SoyMilk>(std::move(order3));
order3 = std::make_unique<Mocha>(std::move(order3));
order3 = std::make_unique<Whip>(std::move(order3));
std::cout << "\n[주문 3] 하우스 블렌드 + 두유 + 모카 + 휘핑크림\n";
printOrder(*order3);
// 주문 4: 에스프레소 + 우유 + 모카
std::unique_ptr<Beverage> order4 = std::make_unique<Espresso>();
order4 = std::make_unique<Milk>(std::move(order4));
order4 = std::make_unique<Mocha>(std::move(order4));
std::cout << "\n[주문 4] 에스프레소 + 우유 + 모카\n";
printOrder(*order4);
return 0;
}