design patterns strategy 戦略パターンを使用する利点はどこですか?



design-patterns strategy-pattern (7)

私は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を強く信じており、より簡単で難読化の少ない技術になりがちです。 ほとんどの場合、私は簡単に保守可能なコードを渡したいと思っています(そして、私はおそらく変更を加えなければならないでしょう)。


Answer #1

戦略と意思決定/選択の間には違いがあります。 たいていの場合、私たちはコード内で意思決定/選択を処理し、if()/ switch()構文を使ってそれらを実現します。 ストラテジーパターンは、ロジック/アルゴリズムの使用を切り離す必要がある場合に便利です。

例として、異なるユーザーがリソース/更新をチェックするポーリングメカニズムについて考えてみましょう。 今や、賞賛されるユーザーの一部に、より迅速な納期や詳細を通知することを希望する場合があります。 要するに、使用されるロジックはユーザーの役割に基づいて変化します。 戦略は設計/アーキテクチャの観点からは理にかなっていますが、細かいレベルでは常に疑問を抱く必要があります。


Answer #2

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

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


Answer #3

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

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

これは、ディレクトリやファイルなどを処理するための別のコードを書くことなく、これらのさまざまなタイプの "ファイル"すべてを扱う汎用コードを書くことができるということを意味します。Unixカーネルは呼び出しを適切なコードに委譲します。

さて、これはカーネルコードで使われているStrategy Patternですが、ユーザーコードでなければならないと指定していません。実世界の例です。 :-)


Answer #4

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

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

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


Answer #5

要点は、実行時にプラグインできるクラスにアルゴリズムを分離することです。 たとえば、クロックを含むアプリケーションがあるとします。 時計を描く方法はいろいろありますが、大半の場合、基本的な機能は同じです。 クロック表示インターフェースを作成することができます:

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

次に、Clockクラスがタイマーに接続され、1秒に1回クロック表示が更新されます。 だからあなたは次のようなものを持っています:

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クラスを混乱させることなく、維持したりデバッグするのが面倒なメソッドをオーバーライドしなくても、新しいクラスを作成して、新しいタイプの時計表示を追加することができます。


Answer #6

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

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

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

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

一般的には、 何をする必要があるを知っているオブジェクトがあればいつでも戦略パターンを使用しますが、それを行う方法はありません。 もう一つの良い例は、スイングのレイアウトマネージャですが、その場合は、まったく同じように動作しませんでした。面白い例としてTotally GridBagを見てください。

注意:ここでは、ストリームにストリームをラップすることがDecoratorの例であるため、ここでは2つのパターンがあります。


Answer #7

戦略パターンは、実行時に戦略/アルゴリズムを変更できるように、単純なアイデア、すなわち「継承上の合成を優先」で動作します。 たとえば、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();
    }
};

今すぐ実行時に、異なる暗号化装置(CDESEncryptor:public CEncryptorなど)を使用してCMessage(CMailMessage:public CMessageなど)から継承したさまざまなメッセージをインスタンス化できます。

CMessage *ptr = new CMailMessage();
ptr->setEncryptor(new CDESEncrypto());
ptr->performEncryption();




strategy-pattern