戦略パターンとコマンドパターンの使用


121

どちらのデザインパターンもアルゴリズムをカプセル化し、実装の詳細を呼び出し元のクラスから切り離します。私が識別できる唯一の違いは、ストラテジーパターンが実行用のパラメーターを取り込むのに対し、コマンドパターンは受け取らないことです。

コマンドパターンは、作成時に実行のためにすべての情報が利用可能である必要があり、その呼び出しを(おそらくスクリプトの一部として)遅らせることができるようです。

1つのパターンと他のパターンのどちらを使用するかは、どの決定によって決まりますか?

回答:


94

これら2つのパターンの違いを説明するために、いくつかのGoFデザインパターンのカプセル化階層テーブルを含めています。うまくいけば、それがそれぞれがカプセル化するものをよりよく説明するので、私の説明はより意味があります。

まず、階層には、特定のパターンが適用されるスコープ、またはテーブルのどちら側から開始するかに応じて、詳細レベルをカプセル化するために使用する適切なパターンが一覧表示されます。

設計パターンのカプセル化階層テーブル

この表からわかるように、戦略パターンオブジェクトはアルゴリズムの実装の詳細を隠しているため、異なる戦略オブジェクトを使用しても同じ機能を実行できますが、方法は異なります。各戦略オブジェクトは、特定の要因に合わせて最適化されるか、他のパラメーターで動作します。また、共通のインターフェースを使用することで、コンテキストはどちらでも安全に機能します。

コマンドパターンは、アルゴリズムよりもはるかに小さな詳細レベルをカプセル化します。オブジェクトにメッセージを送信するために必要な詳細(レシーバー、セレクター、引数)をエンコードします。プロセス実行のこのようなごく一部をオブジェクト化することの利点は、そのようなメッセージを、詳細をハードコードする必要なしに、一般的な方法で異なる時点または場所に沿って呼び出すことができることです。これにより、特定の呼び出しの詳細を実行前に知る必要なく、メッセージを1回以上呼び出すか、システムのさまざまな部分または複数のシステムに渡すことができます。

設計パターンでは一般的ですが、パターン名を付けるためにすべての実装が詳細に同一である必要はありません。詳細は、実装や、オブジェクト内でエンコードされるデータと、メソッド引数としてエンコードされるデータとで異なります。


「フィルターパイプライン」で結果をフィルター処理し、デリゲートをフィルターとして使用するシステムがある場合(フィルターの各アルゴリズムが関数内にカプセル化される場合)、それはコマンドパターンと見なされますか?この場合、フィルター関数のデリゲートは、各フィルターが入力と出力に関して順守しなければならないものについてのソートのコントラクトを提供していると考えています。
KTF 2012

4
@KTF、いいえ。Commandパターンは、オブジェクトのメソッドを呼び出すために必要な情報(たとえば、レシーバー、セレクター、引数)のほとんど(すべてではない)を持つオブジェクトを使用します。これは単純化したパターンであり、責任の連鎖、コレクション、記述したパイプラインパターンなど、他のデザインパターンで使用できます。デリゲートによって提供される「契約の種類」は、別のパターンであるインターフェースです。
Huperniketes 2012年

50

戦略はアルゴリズムをカプセル化します。コマンドは、要求の送信者と受信者を分離し、要求をオブジェクトに変換します。

それがアルゴリズムの場合、どのように実行されるか、戦略を使用します。メソッドの呼び出しとその実行を分離する必要がある場合は、コマンドを使用します。コマンドは、タスクやトランザクションなど、後で使用するためにメッセージをキューに入れるときによく使用されます。


その作られたセンスen.wikipedia.org/wiki/Command_Patternのクライアントと呼び出しが結ばれているが、同時に、彼らはお互いを知っていけません!
Kalpesh Soni、

26

非常に古い質問に答えます。(誰かが最も投票されたのではなく最新の回答を見ていますか?)

似ているため、混乱するのは当然です。戦略パターンとコマンドパターンはどちらもカプセル化を利用しています。しかし、それはそれらを同じにしません。

主な違いは、がカプセル化されているを理解することです。OOの原則は、どちらのパターンも依存し、変化するものをカプセル化することです。

戦略の場合、変化するのはアルゴリズムです。たとえば、1つの戦略オブジェクトはXMLファイルに出力する方法を知っており、他のオブジェクトはJSONなどに出力します。異なるアルゴリズムは、異なるクラスに保持(カプセル化)されます。それはそれと同じくらい簡単です。

コマンドの場合、変化するのはリクエスト自体です。要求から来るかもしれないFile Menu > Deleteか、Right Click > Context Menu > DeleteまたはJust Delete Button pressed。3つのケースすべてで、同じタイプの3つのコマンドオブジェクトを生成できます。これらのコマンドオブジェクトは、削除の3つの要求のみを表します。削除アルゴリズムではありません。リクエストはオブジェクトの束なので、簡単に管理できました。突然、取り消しややり直しなどの機能を提供することは簡単になります。

コマンドが要求されたロジックをどのように実装するかは重要ではありません。execute()を呼び出すと、削除をトリガーするアルゴリズムを実装したり、削除を他のオブジェクトに委任したり、戦略に委任したりすることもできます。コマンドパターンの実装詳細のみです。それは次のように命名された理由はここにあるコマンド、それはへの丁寧な方法はありませんが、要求: - )

戦略とは対照的です。このパターンは、実行される実際のロジックにのみ関係します。そうすることで、最小限のクラスセットでさまざまな動作の組み合わせを実現でき、クラスの爆発を防ぐことができます。

Commandはカプセル化の理解を広げるのに役立ち、Strategyはカプセル化とポリモーフィズムの自然な使用を提供します。


15

私の見方では、同じことを行うには複数の方法があり、それぞれが戦略であり、実行時に何かがどの戦略を実行するかを決定します。

最初にStrategyOneを試してみてください。結果が十分でない場合は、StrategyTwoを試してください...

コマンドは、TryToWalkAcrossTheRoomCommandのように、発生する必要のある明確なものにバインドされています。このコマンドは、一部のオブジェクトが部屋を横断しようとするたびに起動されますが、その内部では、部屋を横断しようとするためにStrategyOneおよびStrategyTwoを試行する場合があります。

マーク


2
RE:「同じことを行う複数の方法」-これは、戦略の一般的な例のいくつかと矛盾するようです。具体的には、加算、減算、乗算などを実行する実装クラスがあるクラスです。おそらくそれらは良い例ではありませんか?
ジョシュアデイビス

1
@JoshuaDavisこれらすべての「サブストラタジー」は、算術演算という 1つの戦略の特殊なケースです。それらは共通の引数(2つのオペランド)を持ち、結果として1つの値を生成します。実装に応じて、同じこと(ブラックボックスであること)を独自の異なる方法で行う。だから私はここで衝突はないが、まったく反対:良い例=)
jungle_mole

7

私の考えでは間違っているかもしれませんが、私はコマンドを実行する機能、または反応として扱います。少なくとも2人のプレーヤーが必要です。アクションを要求するプレーヤーと、アクションを実行するプレーヤーです。GUIはコマンドパターンの典型的な例です。

  • アプリケーションツールバーのすべてのボタンは、いくつかのアクションに関連付けられています。
  • この場合、ボタンは実行者です。
  • この場合のアクションはコマンドです。

コマンドは通常、いくつかのスコープまたはビジネスエリアにバインドされますが、必須ではありません。請求書を発行したり、ロケットを起動したり、execute()1つのアプリケーション内で同じインターフェイス(たとえば、単一のメソッド)を実装するファイルを削除したりするコマンドがある場合があります。多くの場合、コマンドは自己完結型であるため、意図したタスクを処理するためにエグゼキュータから何も必要ありません(必要な情報はすべて構築時に提供されます)。コマンドは状況依存であり、このコンテキストを検出できるはずです(Backspaceコマンドは、前の文字を正しく削除するためにテキスト内のキャレット位置を知っている必要があります。Rollbackコマンドは、現在のトランザクションを検出してロールバックする必要があります; ...)

戦略は少し異なっている:それはいくつかのエリアにより結合しています。戦略は、日付をフォーマットするルール(UTC?ロケール固有?)( "日付フォーマッター"戦略)、または幾何学的図形の正方形を計算するルール( "正方形計算機"戦略)を定義します。戦略とは、この意味で、フライウェイトオブジェクトであり、何かを入力(「日付」、「図」など)として取り、それに基づいて何らかの決定を行います。おそらく戦略のない最高の良い例は、と接続された一方でjavax.xml.transform.Sourceインターフェース:渡されたオブジェクトがあるかどうかに応じてDOMSource、またはSAXSourceあるいはStreamSource戦略(= XSLTトランスこの場合)、それを処理するための異なるルールを適用します。実装は単純なものでswitch、責任の連鎖パターンを伴うものでもかまいません。

しかし、実際、これら2つのパターンの間には共通点があります。コマンドと戦略は、同じセマンティック領域内でアルゴリズムをカプセル化します。


1
コマンドをコールバック関数またはリアクションとして扱います。少なくとも2人のプレーヤーがいるはずです。1人はアクションを要求し、もう1人は実行します...-私はあなたが言っていることを理解していますが、「コールバック」という言葉の使用は遠慮します。 'callback'は非同期呼び出しを意味し、コマンドパターンを使用するために非同期呼び出しを行う必要はありません。適例:Microsoft Word。ツールバーボタンのクリックとショートカットキーの押下は非同期コマンドを起動しませんが、この場合にコマンドパターンがどのように役立つかを理解できます
Jim G.

私は同意しますが、ジムが言ったように、コールバックへの参照を削除するように編集します。
JARC、2012

おかげで、私はいくつかの拡張を行いました。同意する/同意しない場合はお知らせください。
dma_k

5

コマンド:

基本コンポーネント:

  1. コマンドは、次のような抽象コマンドのインターフェースを宣言しますexecute()
  2. 受信者は特定のコマンドを実行する方法を知っています
  3. 実行者は、実行する必要がある ConcreteCommandを保持します。
  4. クライアント具象コマンドを作成し、レシーバーを割り当てます
  5. ConcreteCommandは、コマンドレシーバーの間のバインディングを定義します

ワークフロー:

クライアントInvokerを呼び出す=> InvokerConcreteCommandを呼び出す => ConcreteCommandが、抽象的なCommandメソッドを実装するReceiverメソッドを呼び出す。

利点:クライアントはコマンドとレシーバーの変更に影響を与えません。インボーカーは、クライアントとレシーバーの間の疎結合を提供します。同じInvokerで複数のコマンドを実行できます。

コマンドパターンを使用すると、同じ Invokerを使用して異なるレシーバーでコマンドを実行できます。呼び出し側はレシーバーのタイプを認識していません

概念をよりよく理解するには、Wikipediaのリンクに加えて、Pankaj KumarによるこのJournalDevの記事James Sugrueによるdzoneの記事をご覧ください

コマンドパターンを使用して

  1. コマンドの呼び出し側と受信側を分離する

  2. コールバックメカニズムを実装する

  3. 元に戻すおよびやり直し機能を実装する

  4. コマンドの履歴を維持する

java.lang.Threadコマンドパターンの1つの優れた実装です。スレッドはRunnableを具現化するインボーカーおよびクラスとして、ConcreteCommonad / Receiverrun()として、メソッドとしてはCommandとして扱うことができます。

コマンドパターンの元に戻す/やり直しバージョンは、Theodore Norvellの 記事で読むことができます。

戦略:

戦略パターンは非常に簡単に理解できます。このパターンは

アルゴリズムには複数の実装があり、アルゴリズムの実装は特定の条件に応じて実行時に変更される可能性があります

航空会社予約システムの運賃コンポーネントの例を見てみましょう

航空会社は、ピーク期間とオフピーク期間の異なる期間に異なる運賃を提供したいと考えています。オフピークの旅行日には、魅力的な割引を提供することで需要を刺激したいと考えています。

戦略パターンの重要なポイント:

  1. それは行動パターンです
  2. 委任に基づいています
  3. メソッドの動作を変更することにより、オブジェクトの根性を変更します
  4. アルゴリズムのファミリーを切り替えるために使用されます
  5. 実行時にオブジェクトの動作を変更します

コード例を含む関連記事:

コマンドデザインパターンの使用

戦略パターンの実例


0

私にとって、違いは意図の1つです。両方のパターンの実装はかなり似ていますが、目的が異なります。

  • ストラテジーの場合、オブジェクトを使用するコンポーネントはオブジェクトが何をするかを知っいます(そしてそれを使用して独自の作業の一部を実行します)が、それがどのように行われるかは気にしません。

  • コマンドの場合は、オブジェクトを使用してコンポーネントはどちらも知らないのコマンドがありませんでもどのように、それはそれをしない-それはちょうどそれを起動する方法を知っています。呼び出し元のタスクは、コマンドを実行することだけです。コマンドによって実行される処理は、呼び出し元の中核作業の一部を形成しません。

これが違いです-コンポーネントを使用するオブジェクトは、コンポーネントが何をするかを実際に知っているか、気にしますか?ほとんどの場合、これはパターンオブジェクトが呼び出し元に値を返すかどうかに基づいて決定できます。呼び出し元がパターンオブジェクトの動作に関心がある場合、おそらく何かを返すようにして、それが戦略になります。戻り値を気にしない場合、それはコマンドである可能性があります(注:Java Callableのようなものはまだコマンドです。なぜなら、値を返しますが、呼び出し元は値を気にしないため、単にそれを返します。最初にコマンドを提供したものに)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.