GoF本 Strategy

GoFデザインパターン勉強メモ

ねらい

  • アルゴリズムをカプセル化し、他のアルゴリズムと可換にする
  • アルゴリズム利用側とは独立して変更・拡張できるようにする

AKA

  • Policy

つかいどころ

  • ふるまいだけが異なる似通ったクラスが複数ある
  • いくつかのアルゴリズムを選べるようにしたい

    • 時間・空間・美しさ等のトレードオフ
  • アルゴリズム固有のデータを隠蔽したい

    • 【補】マージソートのO(n)の外部空間とか?
    • アルゴリズム利用側からしたらどうでもいい(知りたくない)データ
  • 同じような分岐がいくつものメソッドにある

    • enumか何かで分岐している
    • enumに対応するStrategyを用意して、これを排することができる

構造

オブジェクトの集約版

20190102181104

ジェネリクス版

20190102181116

  • 制限付き
  • 静的にバインドするので実行時効率が良い

登場人物

  • Strategy

    • 全アルゴリズム共通のインターフェースを定義
  • Concretestrategy

    • Strategyの実装クラス
    • アルゴリズム固有のデータを保持することも
    • 状態をもたなければ、FlyweightやSingletonにできる
  • Context

    • concretestrategyオブジェクトでカスタムできる
    • strategyオブジェクトを保持

      • nullableにして、デフォルト動作をContextに実装する方法もある
    • Strategyからデータアクセスするためのインターフェースを設けることも

クライアントコードからの利用

  • クライアントはconcretestrategyオブジェクトを選択・生成し、contextに渡す
  • 以降、concretestrategyオブジェクトを直接触ることはない
  • contextconcretestrategyオブジェクトに処理を委譲する

    • 必要なデータだけ渡すパターン
    • context丸ごと渡して、concretestrategy側で必要なものを取得するパターン

結果

  • 関連するアルゴリズムが1つのクラスツリーに切り出される

    • ベースクラスに共通の処理を置くことができる

      • 【補】Template Method Pattern
  • 継承ではないアプローチである

    • Contextを継承するデメリット

      • 振る舞いだけ異なるContext派生が大量に生まれる
      • Contextの実装とアルゴリズムの実装とが混然一体となり、変更・拡張が大変になる

        • 【補】「実装は問わず、とりあえずソートさえできればいい」場合

          • qsort(collection)mergesort(collection)とかが並ぶと見通しが悪い
          • sortStrategy->sort(collection); と抽象的に書けたほうが見通しが良い
      • オブジェクト生成後にアルゴリズムを動的に変更できない
  • 条件分岐がなくなる

    • Contextの中でenumでアルゴリズムを分岐していたらStrategy Patternを適用すべきサイン

      • Strategy Patternを知らない人が、
      • Contextの継承も行わずに、
      • 複数のアルゴリズムを選択可能にするとこうなる
  • ブラックボックスの実装を選択可能にできる

    • 入出力は同じだが、時間と空間のトレードオフが異なる場合など
    • 【補】ソートアルゴリズム

      • 要素数が少なければバブルソート等がよい
      • 増えてきたらquick sort等がよい
  • クライアントはStrategyのバリエーションを知っている必要がある

    • 適切なものを選択するために、その中身についても知っている必要がある
    • 【補】ソートアルゴリズム

      • 要素数がxx以下なら、O(n^2)でもオーバヘッドがない分バブルソートのほうが効率が良い、とか
  • StrategyContextの相互作用のオーバヘッド

    • Concretestrategyごとに必要なパラメータが異なることがある
    • Strategyのインターフェースは和集合的でなければならない
    • したがって、必要ない引数を生成し渡してしまうケースが出てくる
    • ConcretestrategyContextとを密結合にすることで回避可能
  • オブジェクトの数が増える

    • Concretestrategyに状態を持たせず、SingletonやFlyweightにすることで軽減可能

実装にあたり考えるべきこと

  • ContextStrategyのインターフェース

    • ConcretestrategyContextから必要なデータを取得できる必要がある

      • 引数で渡す(push)

        • Concretestrategyで必要なものの和集合になる
        • ので、無駄が生じうる
      • contextを丸ごと渡し、Concretestrategyで必要なものを取得する(pull)

        • 無駄はなくなる
        • ContextConcretestrategyとが密結合してしまう
  • StrategyContext<Strategy>のテンプレート型引数にする

    • 静的にバインドするので実行時効率が良い
    • 制限付き

      • アルゴリズムは静的に決まる
      • アルゴリズムは動的に変わらない
  • strategyオブジェクトをオプショナルにする

    • なければデフォルト動作、あれば処理を委譲
    • 【所感】nullチェックが鬱陶しいのでDefaultStrategyを用意したほうが良くないですか(Null Object Pattern)

関連するパターン

  • Flyweight
  • Singleton

    • ともに、Strategyが状態をもたなければ適用可能

英語

  • factor out

    • 因数分解する
    • 構成要素を取り除く

      • 長くなりすぎた関数の一部を外に追い出すときなど
      • refactorの一つの手段として factoring out がある