web-dev-qa-db-ja.com

状態パターンを正しく使用するにはどうすればよいですか?

私はプログラミング経験で状態パターンのいくつかの実装に遭遇し、いくつか実行しました。私はそれらがさまざまなシナリオ(主にUIと解析)で使用されるのを見てきました。問題は、急速な開発の圧力下にあるそれらすべてが、保守や理解が困難なコードのチャンクになったことです。これらの1つをリファクタリングすることを検討していますが、このオンラインの適切なリソースを見つけるのに苦労しています。オンラインにはState Patternの簡単な例がたくさんありますが、さらに詳しいリソースが必要です。

だから私は探しています:

  • 状態パターンを実装する際の一般的な落とし穴の例とそれらを回避する方法、
  • 正しく行われた状態パターンの実際の例(一部のオープンソースプロジェクト/フレームワークのように)
  • 状態パターンの個人的な経験も大歓迎です

お時間をいただきありがとうございます

41
Ivan

@Ivan:Webには Hierarchical State Machines (HSM)のリソースがたくさんあります。 Miro Samekはこのデザインパターンについて広範囲に書き込み、多くの役立つ情報を提供しています。

興味深い記事:

MealyとMooreによって記述されたフラットなFSMステートチャートよりもHSMを使用する大きな利点は、階層が責任の分離を生み出すことです。サブステートは、明示的に処理するように設計されている条件のみを処理する必要があります。未処理のイベントは、親ステートに渡されるように明示的に設計されていない場合、次の上位に渡されます。親など。これにより、それぞれが単一の目的(単一のオブジェクトに収まる1つの目的)を果たす、管理しやすい小さな状態マシンを作成できます。新しい機能が追加されたとき、または新しいクラスが追加されたときに、それらは世界の独自の小さな部分を処理し、未処理のイベントをそれぞれの親に渡すだけで済みます。

正しく実装すると、循環的複雑度が低く、必要に応じて簡単に変更またはアップグレードできる堅牢なプログラムが得られます。

25
oosterwal

おそらくお読みになったと思いますが、 State Design Pattern は、ステートにそのステートが含まれるオブジェクトの動作がステートによって異なる場合に役立ちます。これは、State抽象クラス、インターフェース、または 列挙型 のアイデアを意味します-ただし、言語によっては Duck Typing でも機能します-一般的な動作および/または必要なメソッド。

主要な側面

状態パターンを操作する際に考慮すべき重要な側面が2つあります。列挙と遷移です。列挙は単に、可能な状態のセット(たとえば、曜日)、またはより抽象的には、ワークフローエンジンの開始、終了、中間などの状態のタイプ(メタ状態)を識別することを意味します。遷移とは、動きをモデル化する方法を決定することですこれは通常、可能なすべての遷移を表形式でキャプチャすることによって(つまり、 Finite State Machine )、各状態に他の状態への可能な「遷移」を知らせることによって行われます。

新しい状態、つまり遷移を実行時に追加できる動的なシステムでは、すべての状態と関係を事前に知ることができないため、通常、遷移はメタ状態と連動します。さらに、遷移アプローチでは、特定の動作(通知など)が状態自体ではなく遷移の一部になります。

これが使用機能である場合、私が取り組んだ、または議論したいくつかのシナリオがあります。

  1. ワークフロー
  2. コンピュータゲームの対戦相手A.I.
  3. プロセスオーケストレーション

workflowとは jBPM のようなものを意味します。このようなシステムは、適切なタイミングで適切な人々に適切な注意を向けることに関係しています。彼らは通常、大量のメールやその他の通知を送信します。また、それらが表すプロセスには、組織の変化に応じて変化する機能が必要ですが、管理されるデータの変化は通常、はるかにゆっくりです。

コンピュータゲームの対戦相手A.I。は自明です。私が書いたものではありませんが、これらのシステムは通常、自己完結型です。つまり、ワークフローとは異なり、ゲームには通常、コンピューターの対戦相手を制御するために使用されるプロセスを変更する機能がありません。

Process Orchestrationはワークフローに似ていますが、人との対話ではなくシステム統合に重点を置いています。 Apache Mule フレームワークは一例です。ここで、状態はステータス(例:開始、処理中、終了)とタイプ(例:ftp統合ポイント、sql統合ポイント)を記述できます。

結論

他の回答とは異なり、状態のカプセル化はソフトウェアシステムの変更を管理する優れた方法だと思います。うまくできていれば、これらの変更を容易にするか、ユーザーが実行時に変更できるようにします。実装の複雑さの増大と引き換えに、柔軟性の向上とトレードオフを行います。したがって、このようなアプローチは、たとえば動作がおそらく非常によく知られており、変更したくないショッピングカートにはおそらく役に立たないでしょう。一方、プロセスが変更される可能性がある場合は、非常によく適合します。

24
orangepips

私の2セントだけでは、コード化していない人が理解するのが難しいため、状態パターンは常に維持するのが難しくなります。以前のCの経験と同じように、私は通常、関数/メソッドポインターの古い標準配列にフォールバックします。行/列の状態/信号で関数ポインタの2次元配列を作成するだけです。理解しやすい。あなたはそれを管理するクラスがあり、複雑さを処理するために他のクラスに委任します...

my2c

8
neuro

ほとんどの場合、状態パターン設計の状態は複数の状態(または状態のサブ状態)を処理するため、維持が困難になります。

状態に選択の種類がある場合、その状態は主に複数の状態を処理します。

私は州をきれいに保つために多くの規律をとります。

これに対する可能な解決策は、より複雑な状態ステートマシン自体(HSM)を作成することです。これは、より少ない状態を処理する必要があるため、上位レベルでより読みやすくなります。

6
Glenner003

Finite State Machine をご覧ください。ほとんどすべての成熟した言語には独自の良い例があります。優先言語を指定していないため、C++の例 Boost FSM library を示します。おそらくそれはあなたが必要とするよりもはるかに複雑ですが、確かにいくつかの設計のヒントを与えることができます

4
Andriy Tylychko

だから私は探しています:

  • 状態パターンを実装する際の一般的な落とし穴の例とそれらを回避する方法、

状態パターンは適切にスケーリングされません。 10個の状態と10個の異なる遷移タイプを持つ状態マシンを想像してみてください。新しい状態を追加することは、状態が10の遷移すべてを定義する必要があることを意味します。新しい遷移を追加するということは、10の州すべてがそれを定義する必要があるということです。つまり、状態マシンが安定していない場合や、状態/遷移が多い場合は、状態パターンを使用しないでください。

  • 正しく行われた状態パターンの実際の例(一部のオープンソースプロジェクト/フレームワークのように)

定義正しく:-) Java https://stackoverflow.com/a/2707195/1168342 はJSFライフサイクル用ですが、遷移は1つしかないと思います。他の答えはどれもStateについて何も言及していません。

  • 状態パターンの個人的な経験も大歓迎です

Head First Design Patternsはガムボールマシンの例を使用しています 状態を示しています。皮肉なことですが、デザインを拡張する(新しい状態または遷移を追加する)たびに、コードが繰り返されます(特に、特定の状態内の無効な遷移の場合)。また、次の状態を誰が決定するかに応じて、個々の状態クラスを互いに結合できます(状態間依存関係)。この回答の最後の説明を参照してください: https://stackoverflow.com/a/30424503/1168342

GoFの本では、 テーブルベースの選択肢 には利点、つまり規則性があると述べています。移行基準を変更するには、(コードではなく)テーブルを変更する必要があります。

3
Fuhrmanator

状態ごとに異なる動作がある場合は、状態パターンを使用する必要があります。実行時にトランジションを再構成する必要があるかもしれません。これを使用するもう1つの理由は、後で状態を追加する必要がある場合があるためです。

ポーンを選択したり、ターゲットスロットを選択したりするためのさまざまなGUI状態があるチャイニーズチェッカーのようなボードゲームを想像してください。各状態でGUIの動作は異なり、一部の入力は処理され、他の入力は無視されます。単純なスイッチ/ケースを使用することは可能ですが、ロジックがカプセル化されるので、状態パターンが便利になり、関連するコードも一緒になります。これにより、他のほとんどまたはすべての状態に影響を与えることなく、新しい状態を簡単に導入できます(遷移を設定する責任者によって異なります。状態はその出力遷移を知っているか、またはコンストラクターなどを使用して実行時に指定できます)。

この例 で確認できるように、GuiControllerはIGuiStateインターフェイスを使用してオンデマンドで動作を変更します。実装 ここで見ることができます

主な落とし穴は、柔軟性が必要な場合にスイッチ/ケースを使用することです。インダイレクションはもう少し時間がかかるので、一定量のかなり単純な状態の場合に推奨します。かなり高速な低レベルのネットワークプロトコルを実装する必要があります。これは通常、オーバーヘッドが大きくなります。

1
mbx

要素のセットを評価する機能を持つ式エバリュエーターを構築しています。状態パターンは、その状態に応じてセットに対して実行できることと実行できないことを区別するのに非常に役立ちます。すなわち:オープン、クローズ、非アクティブ、アクティブなど。 FSMは、囲まれた属性に応じて機能が何をすべきかを定義するためのifelseステートメントの巨大なブロックの必要性をなくすことで、非常に簡単に描画し、コードの複雑さを軽減します。条件をクラスにすることで、これらの条件をより明示的にします。これは、私のお気に入りのパターンの1つです。

0
user5484291