Menu
Coddy logo textTech

ステートパターン

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 クラスは、現在の状態を確認するための条件分岐を必要としません。単に現在アクティブな状態オブジェクトに処理を委譲するだけです。このパターンは、注文処理、メディアプレーヤー、ワークフローシステムのように、明確に異なる振る舞いモードを持つオブジェクトに最適です。

challenge icon

チャレンジ

簡単

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
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン