Strategyパターン
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 95/104。
Strategy パターンはアルゴリズムのファミリーを定義し、それぞれをカプセル化して、相互に交換可能にします。これにより、オブジェクトのコードを変更せずに実行時にその振る舞いを変更できます。つまり、アルゴリズムはそれを使用するクライアントから独立して変化します。
このパターンは3つの部分で構成されます。アルゴリズムの method を宣言する Strategy interface、異なるバリエーションを Implement する Concrete Strategies、そして戦略を使用する Context です。
#include <iostream>
#include <memory>
// Strategy インターフェース
class PaymentStrategy {
public:
virtual void pay(int amount) = 0;
virtual ~PaymentStrategy() = default;
};
// 具象戦略
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";
}
};
// コンテキスト
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を使用します。
チャレンジ
簡単Shipping Calculatorを構築しましょう。Strategy パターンを使用して、さまざまな配送方法に基づく配送料を計算します。これは、実行時にアルゴリズムを切り替える必要がある実践的なシナリオです。同じ荷物でも、ground、air、express のいずれかで配送でき、それぞれ独自の料金計算ロジックを持ちます。
コードを3つのファイルに分けて整理します。
ShippingStrategy.h:strategy interface と具体的な配送 strategy をdefineします。抽象
ShippingStrategyclass を作成し、配送コストを double として返す pure virtual methodcalculateCost(double weight)と、virtual destructor を持たせます。次に、3つの具体的な strategy をImplementします。
GroundShipping:weight 1単位あたり1.5(weight * 1.5)AirShipping:weight 1単位あたり4.0(weight * 4.0)ExpressShipping:weight 1単位あたり6.5に、定額料金10.0を加算(weight * 6.5 + 10.0)
ShippingService.h:配送 strategy を使用する Context class を作成します。ShippingServiceclass は、private member としてstd::unique_ptr<ShippingStrategy>を保持します。以下をImplementします。- 配送 method を変更する
setStrategy(std::unique_ptr<ShippingStrategy> strategy)method - current strategy を使用してコストを計算し、返す
calculateShipping(double weight)method
calculateShippingが呼び出されたときに strategy が設定されていなければ、0.0をreturnします。- 配送 method を変更する
main.cpp:実行時に strategy を切り替える方法を示します。2つの入力を読み取ります。
- 荷物の weight(double)
- 配送 method:
ground、air、またはexpress
ShippingServiceを作成し、入力された method に基づいて適切な strategy を設定します。配送料を計算して出力します。次に、別の strategy に切り替え(入力が
airでなければairを、それ以外の場合はgroundを使用)、同じ weight に対して再度コストを計算します。これにより、実行時に strategy を切り替える強力さがわかります。各コストを小数点以下ちょうど1桁で、それぞれ別の行に出力し、method 名を先頭に付けます。
[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 に処理を委譲するだけです。service class をまったく変更せずに、新しい配送方法(drone delivery や same-day など)を簡単に追加できます。
自分で試してみよう
#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: 入力メソッド ("ground", "air", または "express") に基づいて:
// 1. サービスに適切なストラテジーを設定する
// 2. コストを計算し、次の形式で出力する: "[Method]: $[cost]"
// TODO: 別のストラテジーに切り替える:
// - 入力が "air" だった場合、GroundShipping に切り替える
// - それ以外の場合、AirShipping に切り替える
// 新しいコストを計算して出力する
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタパラメータ付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ徹底解説Rule of Three / Five / Zero復習 - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () の演算子オーバーロード型変換演算子復習 - Matrixクラス自分で練習してみよう: C++オンラインコンパイラ