ステートパターン
CoddyのDartジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 98/110。
Stateパターンは、オブジェクトの内部状態が変化したときにその振る舞いを変更できるようにするもので、まるでオブジェクトのクラスが変わったかのように見せることができます。異なる状態を処理するために複雑な条件分岐を使用する代わりに、各状態を独自のクラスにカプセル化し、振る舞いを現在の状態オブジェクトに委譲します。
このパターンは、現在の状態への参照を保持するContextと、考えられる各状態の振る舞いを定義するStateクラスで構成されています。コンテキストの状態が変化すると、その振る舞いも自動的に変化します:
abstract class DocumentState {
void publish(Document doc);
String getStatus();
}
class DraftState implements DocumentState {
@override
void publish(Document doc) {
print('Moving to review...');
doc.setState(ReviewState());
}
@override
String getStatus() => 'Draft';
}
class ReviewState implements DocumentState {
@override
void publish(Document doc) {
print('Document published!');
doc.setState(PublishedState());
}
@override
String getStatus() => 'Under Review';
}
class PublishedState implements DocumentState {
@override
void publish(Document doc) {
print('Already published.');
}
@override
String getStatus() => 'Published';
}
class Document {
DocumentState _state = DraftState();
void setState(DocumentState state) => _state = state;
void publish() => _state.publish(this);
String getStatus() => _state.getStatus();
}
void main() {
var doc = Document();
print(doc.getStatus()); // 下書き
doc.publish(); // レビューへ移動中...
print(doc.getStatus()); // レビュー中
doc.publish(); // ドキュメントが公開されました!
print(doc.getStatus()); // 公開済み
}各状態クラスは publish() アクションを異なる方法で処理し、ドキュメントを次の状態に遷移させることができます。Document クラスは、現在の状態を確認するための条件分岐を必要としません。単に現在アクティブな状態オブジェクトに処理を委譲するだけです。このパターンは、注文処理、メディアプレーヤー、ワークフローシステムのように、明確に異なる振る舞いモードを持つオブジェクトに最適です。
チャレンジ
簡単Stateパターンを使用して信号機システムを構築しましょう!異なる状態を循環する信号機を作成します。各状態は信号の色と、次の状態への遷移方法を決定します。
コードは2つのファイルに分けて構成します:
traffic_light.dart: このファイルには、状態クラスと信号機のコンテキストが含まれます。抽象クラスTrafficLightStateを作成し、現在の信号の色を返すString getColor()と、信号を次の状態に遷移させるvoid next(TrafficLight light)の2つのメソッドを定義してください。 3つの具体的な状態クラスを実装します:RedState- 色として'RED'を返し、next()が呼び出されたときにStop! Light is red.と出力し、その後GreenStateに遷移します。GreenState- 色として'GREEN'を返し、next()が呼び出されたときにGo! Light is green.と出力し、その後YellowStateに遷移します。YellowState- 色として'YELLOW'を返し、next()が呼び出されたときにCaution! Light is yellow.と出力し、その後RedStateに遷移します。
RedStateから開始するTrafficLightクラス(コンテキスト)を作成します。現在の状態を変更するためのsetState()メソッド、現在の状態に処理を委譲するgetColor()メソッド、および現在の状態のnext()を呼び出すchange()メソッドを持たせる必要があります。main.dart: 信号機のファイルをインポートし、Stateパターンの動作を実演します。TrafficLightを作成し、その初期の色を出力します。その後、change()を3回呼び出し、各変更の後に色を出力します。これにより、信号がすべての状態を循環して最初に戻る様子が示されます。
TrafficLight クラスが振る舞いを決定するために条件分岐を必要としないことに注目してください。各状態が自身の法律と次にどの状態が来るかを知っています。信号機は単に現在アクティブな状態に処理を委譲するだけです!
期待される出力:
RED
Stop! Light is red.
GREEN
Go! Light is green.
YELLOW
Caution! Light is yellow.
REDチートシート
State パターンは、オブジェクトの内部状態が変化したときに、その振る舞いを変更できるようにします。複雑な条件分岐を使用する代わりに、各状態を独自のクラスにカプセル化します。
このパターンは以下で構成されます:
- Context(コンテキスト):現在の状態への参照を保持します
- State(状態)クラス:可能な各状態の振る舞いを定義します
基本的な構造:
abstract class DocumentState {
void publish(Document doc);
String getStatus();
}
class DraftState implements DocumentState {
@override
void publish(Document doc) {
print('Moving to review...');
doc.setState(ReviewState());
}
@override
String getStatus() => 'Draft';
}
class Document {
DocumentState _state = DraftState();
void setState(DocumentState state) => _state = state;
void publish() => _state.publish(this);
String getStatus() => _state.getStatus();
}コンテキストは振る舞いを現在の状態オブジェクトに委譲します。各状態クラスはアクションを異なる方法で処理し、次の状態に遷移させることができます。これにより、現在の状態を確認するための条件分岐が不要になります。
注文処理、メディアプレーヤー、ワークフローシステムなど、明確に異なる動作モードを持つオブジェクトに最適です。
自分で試してみよう
import 'traffic_light.dart';
void main() {
// TODO: TrafficLight のインスタンスを作成する
// TODO: 初期の色を表示する
// TODO: change() を呼び出し、色を表示する(すべての状態を循環させるために3回繰り返す)
// 1回目の変更: RED -> GREEN
// 2回目の変更: GREEN -> YELLOW
// 3回目の変更: YELLOW -> RED
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4Null Safety
Null Safety 入門Nullable と Non-Nullable? 演算子と ! 演算子late キーワードと Null SafetyNull-Aware 演算子クラスにおける Null Safetyまとめ:ユーザープロフィールシステム10コレクションとジェネリクス
List, Set, Map の概要型安全なコレクションジェネリッククラスジェネリックメソッドジェネリクスの制約Iterable と Iteratorまとめ:ジェネリックなストレージ