데코레이터 패턴
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 104개 중 98번째.
Decorator 패턴을 사용하면 객체를 데코레이터라고 하는 특수한 객체로 감싸 객체에 새로운 동작을 동적으로 추가할 수 있습니다. 컴파일 시점에 동작을 추가하는 상속과 달리, 데코레이터를 사용하면 원래 클래스를 수정하지 않고 런타임에 기능을 확장할 수 있습니다.
이 패턴은 원래 객체와 데코레이터가 모두 동일한 interface를 구현하도록 하여 작동합니다. 각 데코레이터는 component에 대한 참조를 보유하고, 래핑된 객체에 위임하기 전이나 후에 자체 동작을 추가합니다.
#include <iostream>
#include <memory>
// 컴포넌트 인터페이스
class Coffee {
public:
virtual std::string getDescription() const = 0;
virtual double getCost() const = 0;
virtual ~Coffee() = default;
};
// 구체 컴포넌트
class SimpleCoffee : public Coffee {
public:
std::string getDescription() const override { return "Coffee"; }
double getCost() const override { return 2.0; }
};
// 기본 데코레이터
class CoffeeDecorator : public Coffee {
protected:
std::unique_ptr<Coffee> coffee;
public:
CoffeeDecorator(std::unique_ptr<Coffee> c) : coffee(std::move(c)) {}
};
// 구체 데코레이터
class MilkDecorator : public CoffeeDecorator {
public:
MilkDecorator(std::unique_ptr<Coffee> c) : CoffeeDecorator(std::move(c)) {}
std::string getDescription() const override {
return coffee->getDescription() + ", Milk";
}
double getCost() const override { return coffee->getCost() + 0.5; }
};
class SugarDecorator : public CoffeeDecorator {
public:
SugarDecorator(std::unique_ptr<Coffee> c) : CoffeeDecorator(std::move(c)) {}
std::string getDescription() const override {
return coffee->getDescription() + ", Sugar";
}
double getCost() const override { return coffee->getCost() + 0.2; }
};decorator를 쌓을 수 있습니다. 각 decorator가 이전 것을 감싸면서 기능을 계층별로 구축합니다:
int main() {
std::unique_ptr<Coffee> order = std::make_unique<SimpleCoffee>();
order = std::make_unique<MilkDecorator>(std::move(order));
order = std::make_unique<SugarDecorator>(std::move(order));
std::cout << order->getDescription() << ": $" << order->getCost();
// 출력: Coffee, Milk, Sugar: $2.7
}다른 객체에 영향을 주지 않고 객체에 책임을 동적으로 추가해야 하거나, 서브클래싱을 통해 기능을 확장하면 클래스가 폭발적으로 늘어나는 경우 Decorator를 사용하세요.
챌린지
쉬움데코레이터 패턴을 사용하여 Pizza Order System을 만들어 봅시다. 고객이 기본 피자로 시작한 다음 다양한 토핑을 추가할 수 있는 유연한 시스템을 만들게 됩니다. 각 토핑은 이전 주문을 감싸고 자체 비용과 설명을 추가합니다. 이는 데코레이터를 사용하기에 완벽한 사례입니다. 서브클래스가 폭발적으로 늘어나지 않고도 토핑을 어떤 조합으로든 쌓을 수 있기 때문입니다.
코드를 세 개의 파일로 구성합니다.
Pizza.h: 컴포넌트 인터페이스와 기본 피자 클래스를 정의합니다.순수 가상 메서드
getDescription()(문자열 반환)와getCost()(double 반환), 그리고 가상 소멸자를 포함하는 추상Pizza클래스를 만듭니다.그런 다음 기본 피자를 나타내는 구체적인
PlainPizza클래스를 만듭니다. 설명으로"Pizza"를 반환하고 기본 비용은8.0이어야 합니다.ToppingDecorator.h: 데코레이터 기본 클래스와 구체적인 토핑 데코레이터를 만듭니다.Pizza를 상속하고 감싸진 컴포넌트를 가리키는std::unique_ptr<Pizza>를 보유하는ToppingDecorator클래스를 만듭니다. 이 클래스는 모든 토핑 데코레이터의 기본 클래스 역할을 합니다.세 개의 구체적인 토핑 데코레이터를 구현합니다.
CheeseTopping: 설명에", Cheese"를 추가하고 비용에1.5를 추가합니다.PepperoniTopping: 설명에", Pepperoni"를 추가하고 비용에2.0를 추가합니다.MushroomTopping: 설명에", Mushrooms"를 추가하고 비용에1.0을 추가합니다.
각 데코레이터는 감싸진 피자에 처리를 위임한 다음 자체 기여분을 추가해야 합니다.
main.cpp: 사용자 입력을 바탕으로 맞춤형 피자 주문을 만듭니다.세 가지 입력을 읽습니다(각 입력은
yes또는no입니다).- 치즈를 추가할까요?
- 페퍼로니를 추가할까요?
- 버섯을 추가할까요?
PlainPizza로 시작한 다음 입력에 따라 적절한 토핑 데코레이터로 감쌉니다(순서: 치즈, 페퍼로니, 버섯). 마지막으로 다음 형식으로 주문 세부 정보를 출력합니다.Order: [description] Total: $[cost]
예를 들어 입력이 yes, yes, no인 경우:
Order: Pizza, Cheese, Pepperoni
Total: $11.5입력이 yes, no, yes인 경우:
Order: Pizza, Cheese, Mushrooms
Total: $10.5입력이 no, no, no인 경우:
Order: Pizza
Total: $8입력이 yes, yes, yes인 경우:
Order: Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.5각 데코레이터가 이전 데코레이터를 감싸면서 설명과 비용을 계층별로 쌓아 올리는 방식을 확인해 보세요. 기존 피자 또는 토핑 코드를 수정하지 않고도 새로운 데코레이터 클래스를 만들어 새 토핑을 쉽게 추가할 수 있습니다. 이것이 바로 데코레이터 패턴의 장점입니다.
직접 해보기
#include <iostream>
#include <string>
#include <memory>
#include "Pizza.h"
#include "ToppingDecorator.h"
using namespace std;
int main() {
// 세 개의 입력 읽기 (yes 또는 no)
string addCheese, addPepperoni, addMushrooms;
cin >> addCheese;
cin >> addPepperoni;
cin >> addMushrooms;
// TODO: std::unique_ptr를 사용하여 PlainPizza로 시작
// TODO: addCheese가 "yes"이면 피자를 CheeseTopping으로 감싸기
// TODO: addPepperoni가 "yes"이면 피자를 PepperoniTopping으로 감싸기
// TODO: addMushrooms가 "yes"이면 피자를 MushroomTopping으로 감싸기
// TODO: 다음 형식으로 주문을 출력:
// 주문: [description]
// 총액: $[cost]
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
직접 연습해 보세요: 온라인 C++ 컴파일러