質問

Wikipediaのこの説明、特に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を強く信じており、より簡単で難読化の少ない技術を使用する傾向があります。ほとんどの場合、簡単に保守可能なコードを渡したいので(そして、「変更を加えなければならないのはおそらく私です!」

役に立ちましたか?

解決

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

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

次に、タイマーに接続され、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クラスを台無しにしたり、メンテナンスやデバッグが面倒なメソッドをオーバーライドしたりせずに、新しいクラスを作成することで、後で任意のタイプの新しいクロック表示を追加できます。

他のヒント

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

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

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

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

一般に、戦略パターンは、実行する必要があるが方法ではなくを知っているオブジェクトがある場合は常に使用します。もう1つの良い例はSwingのレイアウトマネージャーですが、その場合はうまく機能しませんでした。 Totally GridBag 面白いイラスト。

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

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

例として、さまざまなユーザーがリソース/更新をチェックするポーリングメカニズムを考えてください。ここで、一部の特権ユーザーに、より速いターンアラウンドタイムまたは詳細を通知するようにしたい場合があります。本質的に、使用されるロジックはユーザーの役割に基づいて変化します。戦略は設計/アーキテクチャの観点から理にかなっており、粒度の低いレベルでは常に疑問視されるべきです。

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

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

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

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

推奨読書:

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

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

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

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

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

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

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

つまり、これらのさまざまな種類の「ファイル」をすべて処理する汎用コードを作成でき、ファイルとディレクトリなどを処理する別のコードを記述する必要はありません。Unixカーネルは、適切なコードへの呼び出しを委任します。

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

戦略パターンは、単純なアイデア、つまり「継承よりも合成を好む」で機能します。実行時に戦略/アルゴリズムを変更できるようにします。例として、タイプに基づいて異なるメッセージを暗号化する必要がある例を見てみましょう。 MailMessage、ChatMessageなど

class CEncryptor
{
    virtual void encrypt () = 0;
    virtual void decrypt () = 0;
};
class CMessage
{
private:
    shared_ptr<CEncryptor> m_pcEncryptor;
public:
    virtual void send() = 0;

    virtual void receive() = 0;

    void setEncryptor(cost shared_ptr<Encryptor>& arg_pcEncryptor)
    {
        m_pcEncryptor =  arg_pcEncryptor;
    }

    void performEncryption()
    {
        m_pcEncryptor->encrypt();
    }
};

実行時に、CMessageから継承した異なるメッセージ(CMailMessage:public CMessageなど)を異なる暗号化プログラム(CDESEncryptor:public CEncryptorなど)でインスタンス化できます

CMessage *ptr = new CMailMessage();
ptr->setEncryptor(new CDESEncrypto());
ptr->performEncryption();
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top