ステートパターン
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 100/104。
Stateパターンは、オブジェクトの内部状態が変化したときにその振る舞いを変更できるようにするもので、まるでオブジェクトのクラスが変わったかのように見せることができます。異なる状態を処理するために複雑な条件文を使用する代わりに、各状態を個別のクラスとしてカプセル化します。
このパターンは、現在の状態への参照を保持する Context、状態固有の振る舞いを定義する State interface、および各状態の振る舞いを実装する Concrete States で構成されています:
#include <iostream>
#include <memory>
class Document; // 前方宣言
// 状態インターフェース
class DocumentState {
public:
virtual void publish(Document& doc) = 0;
virtual std::string getName() const = 0;
virtual ~DocumentState() = default;
};
// コンテキスト
class Document {
std::unique_ptr<DocumentState> state;
public:
Document();
void setState(std::unique_ptr<DocumentState> newState) {
state = std::move(newState);
}
void publish() { state->publish(*this); }
std::string getStateName() const { return state->getName(); }
};
// 具体的な状態
class Draft : public DocumentState {
public:
void publish(Document& doc) override;
std::string getName() const override { return "Draft"; }
};
class Review : public DocumentState {
public:
void publish(Document& doc) override;
std::string getName() const override { return "Review"; }
};
class Published : public DocumentState {
public:
void publish(Document& doc) override {
std::cout << "Already published\n";
}
std::string getName() const override { return "Published"; }
};
void Draft::publish(Document& doc) {
std::cout << "Moving to review\n";
doc.setState(std::make_unique<Review>());
}
void Review::publish(Document& doc) {
std::cout << "Publishing document\n";
doc.setState(std::make_unique<Published>());
}
Document::Document() : state(std::make_unique<Draft>()) {}各状態は publish() アクションを異なる方法で処理し、次の状態への遷移を担当します。 Document は遷移ロジックを知る必要はなく、単に現在の状態に委譲します。これにより、巨大な switch 文が不要になり、新しい状態の追加が容易になります。
オブジェクトの振る舞いがその状態に大きく依存しており、オブジェクトの状態に応じて切り替わる条件分岐が多数存在する場合に、Stateパターンを使用します。
チャレンジ
簡単State パターンを使用して、信号機コントローラー(Traffic Light Controller)を構築しましょう。信号機が異なる状態(Red、Yellow、Green)を循環し、各状態が信号が変わったときに何が起こるか、どのようなメッセージが表示されるかを決定するシステムを作成します。これは State パターンの典型的な例です。信号機の動作は、現在の状態に完全に依存します。
コードは以下の3つのファイルに分けて構成します:
TrafficLightState.h: 状態インターフェースとすべての具体的な状態クラスを定義します。以下の内容を持つ抽象クラス
TrafficLightStateを作成してください:- 次の状態への遷移を処理する純粋仮想関数
change(TrafficLight& light) - 現在の色を文字列として返す純粋仮想関数
getColor() - ドライバーがすべき行動(例:
"Stop"、"Caution"、"Go")を返す純粋仮想関数getAction() - 仮想デストラクタ
3つの具体的な状態を実装してください:
RedState— 色は"Red"、アクションは"Stop"、Green に遷移しますYellowState— 色は"Yellow"、アクションは"Caution"、Red に遷移しますGreenState— 色は"Green"、アクションは"Go"、Yellow に遷移します
状態クラスが
TrafficLightを参照するため、TrafficLightの前方宣言が必要になります。- 次の状態への遷移を処理する純粋仮想関数
TrafficLight.h: 現在の状態を保持するコンテキストクラスを作成します。TrafficLightクラスはstd::unique_ptr<TrafficLightState>を保持し、デフォルトで Red 状態から開始するようにします。以下を実装してください:setState(std::unique_ptr<TrafficLightState> newState)— 現在の状態を変更しますchange()— 現在の状態の change メソッドに処理を委譲しますdisplay()— 信号の状態を[Color]: [Action]の形式で出力します
main.cpp: 信号機が状態を循環する様子を実演します。1つの入力(実行する状態変化の回数、整数)を読み取ります。
TrafficLightを作成し、初期状態を表示します。その後、指定された回数だけ変化を実行し、各変化の後に状態を表示します。
例えば、入力が 3 の場合:
Red: Stop
Green: Go
Yellow: Caution
Red: Stop入力が 6 の場合:
Red: Stop
Green: Go
Yellow: Caution
Red: Stop
Green: Go
Yellow: Caution
Red: Stop信号機が Red → Green → Yellow → Red という予測可能なパターンで状態を循環することに注目してください。各状態は次にどの状態が来るかを知っており、自身の遷移を処理します。TrafficLight クラスは、何が起こるかを決定するための条件分岐ロジックを必要としません。単に現在の状態に処理を委譲するだけです。
自分で試してみよう
#include <iostream>
#include "TrafficLight.h"
using namespace std;
int main() {
// 状態変化の回数を読み込む
int numChanges;
cin >> numChanges;
// TODO: TrafficLightオブジェクトを作成する
// TODO: 初期状態を表示する
// TODO: 指定された回数の変化を実行する
// 各変化の後、現在の状態を表示する
return 0;
}このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタ引数付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ詳解Rule of Three / Five / Zeroまとめ - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () 演算子のオーバーロード型変換演算子まとめ:Matrix クラス