デコレーターパターン
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 98/104。
Decoratorパターンは、オブジェクトをデコレータと呼ばれる特殊なオブジェクトでラップすることにより、オブジェクトに新しい振る舞いを動的に追加することを可能にします。コンパイル時に振る舞いを追加する継承とは異なり、デコレータを使用すると、元のクラスを修正することなく、実行時に機能を拡張できます。
このパターンは、元のオブジェクトとデコレータの両方に同じインターフェースを実装させることで機能します。各デコレータはコンポーネントへの参照を保持し、ラップされたオブジェクトに処理を委譲する前または後に、独自の振る舞いを追加します:
#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; }
};デコレータは積み重ねることができます。各デコレータが前のデコレータをラップし、機能を一層ずつ積み上げていきます。
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を使用します。
チャレンジ
簡単Decorator パターンを使用して、ピザ注文システムを構築しましょう。顧客がベースとなるピザから始めて、さまざまなトッピングを追加できる柔軟なシステムを作成します。各トッピングは前の注文をラップし、独自の価格と説明を追加します。これはデコレータの完璧なユースケースです。サブクラスを大量に作成することなく、トッピングを任意の組み合わせで積み重ねることができます。
コードは以下の3つのファイルに分けて構成します:
Pizza.h: コンポーネントインターフェースとベースとなるピザクラスを定義します。純粋仮想関数
getDescription()(文字列を返す)とgetCost()(doubleを返す)、および仮想デストラクタを持つ抽象クラスPizzaを作成します。次に、ベースとなるピザを表す具体的な
PlainPizzaクラスを作成します。説明として"Pizza"を返し、基本料金を8.0とします。ToppingDecorator.h: デコレータのベースクラスと具体的なトッピングデコレータを構築します。Pizzaを継承し、ラップされたコンポーネントへのstd::unique_ptr<Pizza>を保持するToppingDecoratorクラスを作成します。これがすべてのトッピングデコレータのベースとなります。以下の3つの具体的なトッピングデコレータを実装してください:
CheeseTopping— 説明に", Cheese"を追加し、価格に1.5を追加しますPepperoniTopping— 説明に", Pepperoni"を追加し、価格に2.0を追加しますMushroomTopping— 説明に", Mushrooms"を追加し、価格に1.0を追加します
各デコレータは、ラップされたピザに処理を委譲し、その後に自身の情報を追加する必要があります。
main.cpp: ユーザー入力に基づいてカスタマイズされたピザの注文を作成します。3つの入力(それぞれ
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各デコレータが前のデコレータをラップし、説明と価格の両方を層状に積み上げていく様子に注目してください。既存のピザやトッピングのコードを変更することなく、新しいデコレータクラスを作成するだけで簡単に新しいトッピングを追加できます。これが Decorator パターンの素晴らしい点です。
自分で試してみよう
#include <iostream>
#include <string>
#include <memory>
#include "Pizza.h"
#include "ToppingDecorator.h"
using namespace std;
int main() {
// 3つの入力(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: 次の形式で注文を出力する:
// Order: [description]
// Total: $[cost]
return 0;
}このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタ引数付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ詳解Rule of Three / Five / Zeroまとめ - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () 演算子のオーバーロード型変換演算子まとめ:Matrix クラス