12

私はウィキペディア、特にC ++サンプルでこの説明を見てきましたが、3つのクラスを定義し、インスタンスを作成して呼び出すことと、その例との違いを認識できていません。私が見たのは、他の2つのクラスをプロセスに配置しただけで、どこにメリットがあるのか​​わかりません。今、私は明らかな何か(木のための木材)が欠けていると確信しています-誰かが決定的な実世界の例を使用してそれを説明できますか?


これまでの回答から私ができることは、これを行うためのより複雑な方法のように思えます。

have an abstract class: MoveAlong with a virtual method: DoIt()
have class Car inherit from MoveAlong, 
     implementing DoIt() { ..start-car-and-drive..}
have class HorseCart inherit from MoveAlong, 
     implementing DoIt() { ..hit-horse..}
have class Bicycle inherit from MoveAlong, 
     implementing DoIt() { ..pedal..}
now I can call any function taking MoveAlong as parm 
passing any of the three classes and call DoIt
Isn't this what Strategy intents? (just simpler?)

[編集-更新]上記で参照した関数は、MoveAlongがこの新しいクラスに実装されたアルゴリズムに基づいて必要に応じて設定される属性となる別のクラスに置き換えられます。(受け入れられた回答に示されているものと同様です。)


[編集-更新]結論

ストラテジーパターンには用途がありますが、私はKISSを強く信じており、より単純でわかりにくい手法になる傾向があります。主に、保守が容易なコードを渡したいので(そして、変更を加えなければならないのはおそらく私です!)。

4

8 に答える 8

21

重要なのは、実行時にプラグインできるクラスにアルゴリズムを分離することです。たとえば、時計を含むアプリケーションがあるとします。時計を描く方法はたくさんありますが、基本的な機能はほとんど同じです。したがって、時計表示インターフェイスを作成できます。

class IClockDisplay
{
    public:
       virtual void Display( int hour, int minute, int second ) = 0;
};

次に、タイマーに接続され、1秒に1回時計の表示を更新するClockクラスがあります。したがって、次のようなものになります。

class Clock
{
   protected:
      IClockDisplay* mDisplay;
      int mHour;
      int mMinute;
      int mSecond;

   public:
      Clock( IClockDisplay* display )
      {
          mDisplay = display;
      }

      void Start(); // initiate the timer

      void OnTimer()
      {
         mDisplay->Display( mHour, mMinute, mSecond );
      }

      void ChangeDisplay( IClockDisplay* display )
      {
          mDisplay = display;
      }
};

次に、実行時に、適切な表示クラスを使用してクロックをインスタンス化します。つまり、ClockDisplayDigital、ClockDisplayAnalog、ClockDisplayMartianをすべてIClockDisplayインターフェイスに実装させることができます。

したがって、Clockクラスをいじったり、保守やデバッグが面倒になる可能性のあるメソッドをオーバーライドしたりすることなく、新しいクラスを作成することで、後で任意のタイプの新しい時計表示を追加できます。

于 2008-10-05T10:37:32.143 に答える
10

Javaでは、暗号入力ストリームを使用して次のように復号化します。

String path = ... ;
InputStream = new CipherInputStream(new FileInputStream(path), ???);

ただし、暗号ストリームには、使用する暗号化アルゴリズムやブロックサイズ、パディング戦略などの知識がありません。新しいアルゴリズムが常に追加されるため、それらをハードコーディングすることは実用的ではありません。代わりに、暗号化戦略オブジェクトを渡して、復号化を実行する方法を指示します...

String path = ... ;
Cipher strategy = ... ;
InputStream = new CipherInputStream(new FileInputStream(path), strategy);

一般に、戦略パターンは、何をする必要があるかはわかっているが、どのように行うかはわかっていないオブジェクトがある場合はいつでも使用します。もう1つの良い例は、Swingのレイアウトマネージャーですが、その場合はうまくいきませんでした。面白いイラストについては、TotallyGridBagを参照してください。

注意:ストリームでのストリームのラッピングはデコレータの例であるため、ここでは2つのパターンが機能しています。

于 2008-10-05T10:08:08.600 に答える
5

戦略と決定/選択には違いがあります。ほとんどの場合、コードで決定/選択を処理し、if()/switch() コンストラクトを使用してそれらを実現します。戦略パターンは、ロジック/アルゴリズムを使用から分離する必要がある場合に役立ちます。

例として、さまざまなユーザーがリソース/更新をチェックするポーリング メカニズムを考えてみましょう。現在、一部の特権ユーザーには、より迅速な所要時間または詳細な通知を受け取ることができます。基本的に、使用されるロジックはユーザーの役割に基づいて変化します。戦略は設計/アーキテクチャの観点からは理にかなっていますが、より低いレベルの粒度では常に疑問視する必要があります。

于 2008-10-05T13:22:01.297 に答える
4

戦略パターンを使用すると、メインクラスを拡張せずにポリモルフィズムを活用できます。本質的には、すべての可変部分を戦略インターフェースと実装に配置し、メインクラスはそれらに委任します。メインオブジェクトが1つの戦略のみを使用する場合、それは抽象(純粋仮想)メソッドと各サブクラスに異なる実装を持つこととほぼ同じです。

戦略アプローチにはいくつかの利点があります。

  • 実行時に戦略を変更できます-これを実行時にクラスタイプを変更することと比較してください。これははるかに難しく、コンパイラ固有であり、非仮想メソッドでは不可能です。
  • 1つのメインクラスで複数の戦略を使用できるため、複数の方法でそれらを再結合できます。ツリーを歩き、各ノードと現在の結果に基づいて関数を評価するクラスについて考えてみます。ウォーキング戦略(深さ優先または幅優先)と計算戦略(いくつかの関手-つまり、「正の数を数える」または「合計」)を持つことができます。戦略を使用しない場合は、ウォーキング/計算の組み合わせごとにサブクラスを実装する必要があります。
  • 戦略を変更または理解するためにメインオブジェクト全体を理解する必要がないため、コードの保守が容易です。

欠点は、多くの場合、戦略パターンがやり過ぎであるということです-スイッチ/ケース演算子には理由があります。単純な制御フローステートメント(switch / caseまたはif)から始めて、必要な場合にのみクラス階層に移動し、変動性の次元が複数ある場合は、そこから戦略を抽出することを検討してください。関数ポインタは、この連続体の中間のどこかにあります。

推奨読書:

于 2008-10-05T10:28:43.847 に答える
2

このデザインパターンにより、アルゴリズムをクラスにカプセル化できます。

ストラテジーを使用するクラスであるクライアントクラスは、アルゴリズムの実装から切り離されています。クライアントを変更せずに、アルゴリズムの実装を変更したり、新しいアルゴリズムを追加したりできます。これは動的に実行することもできます。クライアントは、使用するアルゴリズムを選択できます。

たとえば、画像をファイルに保存する必要があるアプリケーションを想像してみてください。画像はさまざまな形式(PNG、JPG ...)で保存できます。エンコーディングアルゴリズムはすべて、同じインターフェイスを共有する異なるクラスに実装されます。クライアントクラスは、ユーザーの好みに応じて1つを選択します。

于 2008-10-05T10:20:34.720 に答える
2

これを見る 1 つの方法は、実行したいさまざまなアクションがあり、それらのアクションが実行時に決定される場合です。戦略のハッシュテーブルまたはディクショナリを作成すると、コマンド値またはパラメーターに対応する戦略を取得できます。サブセットが選択されたら、戦略のリストを単純に反復して、連続して実行できます。

具体的な例の 1 つは、注文の合計を計算することです。パラメータまたはコマンドは、基本価格、地方税、市税、州税、陸送、およびクーポン割引になります。さまざまな注文を処理するときに柔軟性が発揮されます。消費税がない州もあれば、クーポンを適用する必要がある州もあります。計算の順序を動的に割り当てることができます。すべての計算を考慮している限り、再コンパイルせずにすべての組み合わせに対応できます。

于 2008-10-07T23:35:03.477 に答える
1

ウィキペディアの例では、これらのインスタンスは、それらのインスタンスがどのクラスに属しているかを気にする必要のない関数に渡すことができます。関数はexecute渡されたオブジェクトを呼び出すだけで、正しいことが起こることを知っています。

戦略パターンの典型的な例は、Unixでのファイルの動作です。ファイル記述子が与えられるとioctl、ファイル、ディレクトリ、パイプ、ソケット、デバイスを扱っているかどうかを知らなくても、読み取り、書き込み、ポーリング、シーク、送信などを行うことができます。 、など(もちろん、シークなどの一部の操作はパイプやソケットでは機能しません。ただし、これらの場合、読み取りと書き込みは問題なく機能します。)

つまり、ファイルとディレクトリなどを処理するために個別のコードを記述しなくても、これらのさまざまなタイプの「ファイル」をすべて処理するジェネリックコードを記述できます。Unixカーネルは、適切なコードへの呼び出しの委任を処理します。

さて、これはカーネルコードで使用される戦略パターンですが、ユーザーコードである必要があることを指定していません。これは、実際の例にすぎません。:-)

于 2008-10-05T10:07:32.483 に答える