StateパターンとStrategyパターンは何が違うのか考える

todastate, strategy, デザインパターン, 違い

最近、物欲に目覚めてしまってAmazonでいろいろ買ってたら、今月の請求が7万を超えて素に戻ってしまった戸田です。
ちょっとbluetoothデバイスに凝り始めてしまって…。(汗)

さて、オブジェクト指向設計のバイブルと言えば、いわずと知れたGoF本(オブジェクト指向における再利用のためのデザインパターン、Erich Gamma, Ralph Johnson, Richard Helm, John Vlissides著、ソフトバンククリエイティブ刊)です。

ここで紹介されている23のパターンはどれも小手先のテクニックではなく、エッセンスが抽出されており応用範囲が広いものばかりです。
なによりも今まで暗黙知になりがちな、設計の定石・パターンに共通の名称(言語)を与えて、名称による概念の共有ができるようになったという功績は計り知れません。
もちろん、KREISELにおいてもこれらのパターンを活用しております。

ただ、最初GoF本を読み始めたとき、StateパターンとStrategyパターンの何が違うのかがわからなくて悩んだことがありました。クラス構造もほぼ同じだし、処理をState/Strategyクラス側に委譲しているのも同じです。

googleで調べてみると、同じ悩みをもっている人がいる様子。
そこで、両者はどのように使い分けられるべきなのかを自分なりに考察します。


まず、はっきりと断言しますが、State/StrategyこそGoFパターンの真骨頂です。
これはGoF本の中に主張している「継承より委譲の活用」という原則にもっとも忠実なパターンだからです。GoF本より引用します。

オブジェクトコンポジション(注: has-a関係に基づき、オブジェクトが別のオブジェクトを中に持つこと。委譲はオブジェクトコンポジションの応用)がシステム設計に与える影響として、もう1つの点があげられる。クラス継承よりもオブジェクトコンポジションを多用することで、それぞれのクラスをカプセル化し、1つの仕事に集中させることができる。(中略)
 このことはオブジェクト指向設計の2つ目の原則を導く。
 クラス継承よりもオブジェクトコンポジションを多用すること。
(オブジェクト指向における再利用のためのデザインパターン p.31)

たとえば、「図形」、「円」、「三角」、「四角」というクラスを考えます。
単純に考えると「図形」(親クラス) ← 「円」、「三角」、「四角」(子クラス)という継承関係(is-a関係)に表しがちです。

しかし、この方法は拡張性に乏しい方法です。
特に単一継承しかサポートしない言語では、これ以上の拡張のパターンを含めることができません。
多重継承をサポートする言語であっても、複数の拡張のパターンを組み込むためには多重継承にならざるを得えず、クラス構造が複雑になり破綻するのが自明です。

State/Strategyパターンはこの状況に絶大な威力を発揮します。
今までis-a関係として捕らえていた「図形」 ← 「円」という関係を、「図形」が「円」という性質を保持しているというhas-a関係として捕らえ、性質を別の継承ツリーに外だしするわけです。

性質ごとの処理は、本体クラスから委譲によって丸投げすることで対応します。

このことは以下の2つの重大なメリットを生みます。

  • 1. 性質(「円」、「三角」、「四角」)の継承ツリーを元の継承ツリーから分離することが可能になる。
  • 2. また別の性質を「図形」というクラスに組み込むことが可能になる。(多重継承を回避できる)

さてここから本題です。
GoFのStateパターンとStrategyパターンのクラス構造を見ると、両者が酷似していることがわかります。

State
Strategy

両者とも、State(状態)/Strategy(戦略)ごとのアルゴリズムを別ツリーのクラスにしており、実際の処理をそのクラスに委譲するという振る舞いはまったく同一です。
両者の違いをGoFはどう説明しているのか、GoF本よりそれぞれの「目的」の説明を引用します。

State

オブジェクトの内部状態が変化したときに、オブジェクトが振る舞いを変えるようにする。クラス内では、振る舞いの変化を記述せず、状態を表すオブジェクトを導入することによってこれを実現する。
(オブジェクト指向における再利用のためのデザインパターン p.325)

Strategy

アルゴリズムの集合を定義し、各アルゴリズムをカプセル化して、それらを交換可能にする。Strategyパターンを利用することで、アルゴリズムを、それらを利用するクライアントからは独立に変更できるようになる。
(オブジェクト指向における再利用のためのデザインパターン p.335)

これを踏まえたうえで、私個人は以下の通り解釈をするようにしています。

  • そのクラス構造やState, Strategyの役割はまったく同一である。
  • StateとStrategyの違いは、外出しされるState/Strategyが、オブジェクトのライフサイクルの中で、別のものと入れ替えられうるのかの(設計者による)宣言である。
  • 特に、Stateは内部状態が変化しうることをに力点を置いている。

Stateが表すものはベースとなるオブジェクトの「状態」です。
たとえば、TCPセッションというオブジェクトにおいて、その「状態」(未接続、接続中、確立中、切断中etc..)は、ライフサイクル中の外部イベント(相手からの応答があった、切断要求があった、タイムアウトになったetc…)において、別の「状態」に遷移していくものであり、その都度別の「状態」に入れ替わることが前提になります。

一方Strategyが表すものはベースとなるオブジェクトの「種別・タイプ」です。
このタイプはベースのオブジェクトと一体の存在であり、さきほどの例であれば、「三角形」はいつまであっても三角形であって、変わることはありません。(タイプ変換というイベントも考えうることはできますが、これは特別な場合と考えるべきでしょう)

ちなみに上記の説明には「各アルゴリズムをカプセル化して、それらを交換可能にする。」とありますが、これのもともとの原文は

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from the clients that use it.
(Elements of Reusable Object-Oriented Software p.315)

であり、”interchangeable”を「交換可能」と訳していますが、この単語にはgooの辞書によれば

1. 互いに交換{こうかん}[代替{だいたい}]できる、取り換え可能な、置き替え可能な、取り替えても同じように使える[機能{きのう}する]、どちらを使っても変わりはない
2. 〔部品{ぶひん}などが〕互換性{ごかん せい}のある

とあり、後者の「互換性のある」と解釈して、タイプによるアルゴリズムの違いを(Strategyとした)互換性のある形にまとめて、(アルゴリズムを選択することで)クライアントから独立して変化(vary)させることが可能であると考えるのが自然です。

結論としては、設計者がState/Strategyのようなクラス構造を採るときに、State/Strategyによって表されるアルゴリズムが切り替わる頻度の想定によって、どちらの名称を使うかを決めるべきということになります。

尚、State/Strategyの各クラス名称に”XXXXState”, “XXXXStrategy”と名づけて、どちらのパターンであるかを言明することは重要です。
クラス構造上の違いがまったく無い以上、コードの読み手が設計者の意図を把握する手がかりはクラス名にしかないからです。
読み手を混乱させるようなコードを書いてはいけません。(自戒を込めて)

todastate, strategy, デザインパターン, 違い

Posted by toda