C++ Decorator 패턴

객체에 동적으로 새로운 책임을 추가합니다. 서브클래싱 대신 래핑을 통해 기능을 조합하며, 커피 주문처럼 여러 데코레이터를 중첩 적용할 수 있습니다.

Gist
#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;
}