ストラテジーパターン
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 95/104。
Strategyパターンは、一連のアルゴリズムを定義し、それぞれをカプセル化して、それらを交換可能にします。これにより、コードを変更することなく、実行時にオブジェクトの振る舞いを変えることができます。アルゴリズムは、それを利用するクライアントとは独立して変化します。
このパターンは3つの部分で構成されています。アルゴリズムのメソッドを宣言するStrategy interface、異なるバリエーションを実装するConcrete Strategies、そして戦略を使用するContextです:
#include <iostream>
#include <memory>
// Strategy インターフェース
class PaymentStrategy {
public:
virtual void pay(int amount) = 0;
virtual ~PaymentStrategy() = default;
};
// 具体的なストラテジー (Concrete strategies)
class CreditCardPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via Credit Card\n";
}
};
class PayPalPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via PayPal\n";
}
};
// コンテキスト (Context)
class ShoppingCart {
std::unique_ptr<PaymentStrategy> strategy;
public:
void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
strategy = std::move(s);
}
void checkout(int total) {
if (strategy) strategy->pay(total);
}
};
int main() {
ShoppingCart cart;
cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
cart.checkout(100);
cart.setPaymentMethod(std::make_unique<PayPalPayment>());
cart.checkout(50);
}ShoppingCart は、どの支払い方法を使用しているかを知りません。設定されているストラテジーに対して単に pay() を呼び出すだけです。setPaymentMethod() を使用して実行時にストラテジーを切り替えることができるため、システムは柔軟になり、新しい支払いオプションの拡張も容易になります。
特定のタスクに対して複数のアルゴリズムがあり、それらを動的に切り替えたい場合、または振る舞いを選択するための条件文を避けたい場合に、Strategyパターンを使用します。
チャレンジ
簡単異なる配送方法に基づいて配送料を計算するために、Strategy パターンを使用した配送料計算機を作成しましょう。これは、実行時にアルゴリズムを入れ替える必要がある実用的なシナリオです。同じ荷物でも、陸送、空輸、速達など、それぞれ独自の価格ロジックを持つ方法で配送される可能性があります。
コードは以下の3つのファイルに分けて構成します:
ShippingStrategy.h: ストラテジーインターフェースと具体的な配送ストラテジーを定義します。純粋仮想関数
calculateCost(double weight)を持ち、配送料を double 型で返す抽象クラスShippingStrategyを作成し、仮想デストラクタも定義してください。次に、3つの具体的なストラテジーを実装します:
GroundShipping— 重量の単位あたり1.5のコスト (weight * 1.5)AirShipping— 重量の単位あたり4.0のコスト (weight * 4.0)ExpressShipping— 重量の単位あたり6.5のコストに加えて、一律10.0の手数料 (weight * 6.5 + 10.0)
ShippingService.h: 配送ストラテジーを使用するコンテキストクラスを作成します。ShippingServiceクラスは、プライベートメンバとしてstd::unique_ptr<ShippingStrategy>を保持する必要があります。以下を実装してください:- 配送方法を変更するための
setStrategy(std::unique_ptr<ShippingStrategy> strategy)メソッド - 現在のストラテジーを使用してコストを計算し、その結果を返す
calculateShipping(double weight)メソッド
calculateShippingが呼び出されたときにストラテジーが設定されていない場合は、0.0を返してください。- 配送方法を変更するための
main.cpp: 実行時にストラテジーを切り替えるデモンストレーションを行います。2つの入力を読み取ります:
- 荷物の重量 (double)
- 配送方法:
ground、air、またはexpress
ShippingServiceを作成し、入力された配送方法に基づいて適切なストラテジーを設定します。配送料を計算して出力してください。次に、別のストラテジーに切り替え(入力が
airでない場合はairを使用し、そうでない場合はgroundを使用します)、同じ重量に対して再度コストを計算します。これにより、実行時にストラテジーを入れ替える利便性を示します。各コストを、配送方法の名前をプレフィックスとして、小数点以下1桁で個別の行に出力してください:
[Method]: $[cost]
例えば、入力が 5.0 と ground の場合:
Ground: $7.5
Air: $20.0入力が 3.0 と express の場合:
Express: $29.5
Air: $12.0入力が 10.0 と air の場合:
Air: $40.0
Ground: $15.0ShippingService が各価格アルゴリズムの詳細を知る必要がないことに注目してください。単に現在設定されているストラテジーに処理を委譲するだけです。サービス・クラスを一切変更することなく、新しい配送方法(ドローン配送や当日配送など)を簡単に追加できます。
チートシート
Strategyパターンは、一連のアルゴリズムを定義し、それぞれをカプセル化して、それらを交換可能にします。これにより、オブジェクトのコードを変更することなく、実行時にその振る舞いを変更できるようになります。
このパターンは3つの部分で構成されています:
- Strategyインターフェース: アルゴリズムのメソッドを宣言する抽象クラス
- Concrete Strategies(具体的な戦略): 異なるアルゴリズムのバリエーションを実装するクラス
- Context(コンテキスト): Strategyを使用し、それらを切り替えることができるクラス
// Strategyインターフェース
class PaymentStrategy {
public:
virtual void pay(int amount) = 0;
virtual ~PaymentStrategy() = default;
};
// 具体的な戦略(Concrete strategies)
class CreditCardPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via Credit Card\n";
}
};
class PayPalPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via PayPal\n";
}
};
// コンテキスト(Context)
class ShoppingCart {
std::unique_ptr<PaymentStrategy> strategy;
public:
void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
strategy = std::move(s);
}
void checkout(int total) {
if (strategy) strategy->pay(total);
}
};コンテキストは、自分がどの具体的な戦略を使用しているかを知りません。単にインターフェースのメソッドを呼び出すだけです。戦略は、セッターメソッドを使用して実行時に交換できます:
ShoppingCart cart;
cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
cart.checkout(100);
cart.setPaymentMethod(std::make_unique<PayPalPayment>());
cart.checkout(50);いつ使用するか: 特定のタスクに対して複数のアルゴリズムがあり、それらを動的に切り替えたい場合、または振る舞いを選択するための条件文を避けたい場合にStrategyパターンを使用します。
自分で試してみよう
#include <iostream>
#include <string>
#include <iomanip>
#include <memory>
#include "ShippingStrategy.h"
#include "ShippingService.h"
int main() {
double weight;
std::string method;
std::cin >> weight;
std::cin >> method;
// 出力を小数点以下1桁に設定する
std::cout << std::fixed << std::setprecision(1);
ShippingService service;
// TODO: 入力された method ("ground"、"air"、または "express") に基づいて:
// 1. service に適切なストラテジーを設定する
// 2. "[Method]: $[cost]" の形式でコストを計算して出力する
// TODO: 別のストラテジーに切り替える:
// - 入力が "air" だった場合は、GroundShipping に切り替える
// - それ以外の場合は、AirShipping に切り替える
// 新しいコストを計算して出力する
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタ引数付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ詳解Rule of Three / Five / Zeroまとめ - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () 演算子のオーバーロード型変換演算子まとめ:Matrix クラス