ステートパターン
CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 74/87。
State Patternは、内部状態が変化したときにオブジェクトの振る舞いを変えられる振る舞いに関するデザインパターンです。オブジェクトはclassを変更したように見えますが、実際には異なる状態オブジェクトに振る舞いを委譲しています。自動販売機を考えてみてください。ボタンを押したときの応答は、待機中なのか、お金が投入されているのか、それとも商品を払い出しているのかによって異なります。
異なる状態を処理するために複雑な条件文を使う代わりに、それぞれの状態を独自のクラスにカプセル化します。このパターンでは、現在の状態への参照を保持する Context と、各状態の振る舞いを定義する State クラスを使用します。
interface State {
void handle(Context context);
}
class Context {
private State state;
public void setState(State state) {
this.state = state;
}
public void request() {
state.handle(this);
}
}各 concrete state は動作を実装し、他の state への遷移をトリガーできます。
class IdleState implements State {
public void handle(Context context) {
System.out.println("Idle: Insert coin to start");
context.setState(new ActiveState());
}
}
class ActiveState implements State {
public void handle(Context context) {
System.out.println("Active: Processing...");
context.setState(new IdleState());
}
}context は requests を current state に委譲し、state が変化すると behavior も自動的に changes します。
Context context = new Context();
context.setState(new IdleState());
context.request(); // Idle: 開始するにはコインを入れてください
context.request(); // Active: 処理中...State Patternは、オブジェクトの動作がその状態に大きく依存し、実行時に変化する必要がある場合に最適です。大きな条件分岐ブロックをなくし、新しい状態の追加を簡単にします。つまり、Stateインターフェースを実装する新しいclassを作成するだけです。
チャレンジ
簡単State Patternを使用してドキュメントワークフローシステムを構築しましょう!ドキュメントがDraft、Review、Publishedという異なる段階をどのように進むかをモデル化するシステムを作成します。各状態によって、利用可能なactionと、ドキュメントをeditまたはapproveしようとしたときの動作が決まります。
コードを4つのファイルに分けて整理します。
DocumentState.java:すべての状態が実装するDocumentStateインターフェースをDefineします。2つのメソッドをDeclareする必要があります。ドキュメントのeditを試みるためのedit(Document doc)と、ドキュメントをapproveして次の段階へ進めるためのapprove(Document doc)です。States.java:DocumentStateを実装する3つのconcreteな状態クラスをCreateします。DraftState-editがcallされたら、Editing draft...を出力します。approveがcallされたら、Draft approved. Moving to review.を出力し、ドキュメントをReviewStateへ遷移させます。ReviewState-editがcallされたら、Cannot edit during review.を出力します(状態のchangeはありません)。approveがcallされたら、Review complete. Publishing document.を出力し、PublishedStateへ遷移させます。PublishedState-editがcallされたら、Cannot edit published document.を出力します。approveがcallされたら、Document already published.を出力します。どちらのactionでも状態はchangeしません。Document.java:currentの状態を保持するcontextクラスをCreateします。Documentには次のものが必要です。currentの
DocumentStateを保持するprivate field。ConstructorでDraftStateに初期化します。currentの状態をchangeする
setState(DocumentState state)メソッド。currentの状態の
editメソッドにdelegatesするedit()メソッド。currentの状態の
approveメソッドにdelegatesするapprove()メソッド。currentの状態の単純なクラス名を返す
getStatus()メソッド(getClass().getSimpleName()を使用します)。Main.java:Stateシステムを実演します!1つの入力として、comma区切りの文字列によるactionのsequenceを受け取ります。各actionはeditまたはapproveのいずれかです(例:edit,approve,edit,approve)。DocumentをCreateし、初期statusをStatus: [state]というformatで出力します。次に、各actionを順番に処理し、ドキュメント上で適切なメソッドをcallします。各actionのAfterに、currentのstatusを出力します。
1つの入力として、comma区切りのaction文字列を受け取ります。
たとえば、入力がedit,approve,approveの場合、出力は次のようになります。
Status: DraftState
Editing draft...
Status: DraftState
Draft approved. Moving to review.
Status: ReviewState
Review complete. Publishing document.
Status: PublishedState同じapprove()のcallでも、ドキュメントのcurrentの状態によって異なる結果になることに注目してください。これがState Patternの本質です!ドキュメントは状態を確認するために条件分岐を使用しません。その代わりに、動作をcurrentの状態オブジェクトにdelegatesします。状態オブジェクトがactionを適切に処理し、必要な場合には遷移を発生させます。
自分で試してみよう
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
// TODO: 新しい Document を作成する
// TODO: 初期ステータスを "Status: [state]" の形式で出力する
// TODO: 入力をカンマで分割して個々のアクションを取得する
// TODO: 各アクションをループ処理する
// - アクションが "edit" の場合、document に対して edit() を呼び出す
// - アクションが "approve" の場合、document に対して approve() を呼び出す
// - 各アクションの後、現在のステータスを出力する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4継承
継承の基本 (extends)super キーワードメソッドのオーバーライド (@Override)コンストラクタチェーンObject クラス単一継承と多段階継承なぜ多重継承ができないのかまとめ:従業員の階層構造自分で練習してみよう: Javaオンラインコンパイラ