春のセール

Strategy

別名:ストラテジー、戦略

一言でいうと

Strategy ストラテジー 戦略 振る舞いに関するデザインパターンの一つで アルゴリズムのファミリーを定義し それぞれのアルゴリズムを別個のクラスとし それらのオブジェクトを交換可能にします

Strategy デザインパターン

問題

ある日 あなたはカジュアルな旅行者のためのナビゲーション・アプリを作成することにしました このアプリは ユーザーがどの都市でも素早くその地に慣れるのに役立つきれいな地図が特徴でした

アプリの最も要望のあった機能の一つは 自動経路計画でした ユーザーが住所を入力すれば 地図上にその宛先への最速の経路を表示する というものです

アプリの最初のバージョンでは 道路上の経路しか作成できませんでした 車で旅行する人々は喜んでいましたが 休暇中に皆が運転したいわけではありません そこで 次のバージョンでは 徒歩の経路を作成するオプションを加えました そしてそのすぐ後に 公共交通機関を使用した経路のオプションも追加しました

しかし それは始まりに過ぎませんでした その後 サイクリングのための経路作成を計画しました そしてさらに 市内のすべての観光スポットを経由して経路を作成するための別のオプションも加えることにしました

ナビ・アプリのコードは大膨張

ナビ・アプリのコードは大膨張

ビジネスの観点からは アプリは成功でしたが 技術的には 頭痛のタネとなりました 新しい経路アルゴリズムを追加するたびに ナビゲーターの主要クラスのサイズは倍増しました ある時点で この野獣を飼うのに手を焼くようになりました

バグ修正のためであれ 街路スコアの微細な変更であれ アルゴリズムの一つを変更すると クラス全体にその影響が及び 問題なく動いていたコードにエラーを引き起こす可能性が増大しました

さらに チームワークは非効率的になりました 成功したリリースの直後に採用されたチームメートは マージの競合を解決するのに時間を使い過ぎだと苦情を言います 新しい機能を実装するには 他の人が生成したコードと競合するため 巨大なクラスを変更する必要が出てきます

解決策

Strategy パターンに従うと あることを異なる様々な方法で行うクラスに対し これらのアルゴリズムの一つずつを 戦略 と呼ばれる別々のクラスに抽出します

と呼ばれる元のクラスは 数ある戦略のいずれか一つへの参照を格納するためのフィールドを持たなければなりません コンテキストは 自分で作業を行う代わりに リンクされたストラテジー・オブジェクトに作業を委任します

コンテキストは その仕事に適切なアルゴリズムを選択する責任を負いません 代わりに クライアントは希望するストラテジーをコンテキストに渡します 実際のところ コンテキストはストラテジーについての知識を持ちません コンテキストは すべてのストラテジーと 同じ汎用インターフェースを通してやりとりします インターフェースは 選ばれたストラテジーに内包されたアルゴリズムを使用するメソッド一つだけでできています

こうすると コンテキストは具体的なストラテジーからは独立したものとなり コンテキストや他のストラテジーのコードを変更することなく 新しいアルゴリズムを追加したり 既存のものを変更できるようになります

経路計画戦略

経路計画アプリ

我々のナビゲーション・アプリでは それぞれのアルゴリズムを抽出して build­Route メソッド一つだけを持つ固有のクラスを作ります このメソッドは 出発地と目的地を受け取り 経路上の主要箇所のコレクションを返します

同じ引数を与えても 各経路計画クラスは違う経路を作り上げるかもしれません ナビの主クラスは どのアルゴリズムが選択されたかについては まったく感知しません なぜなら 経由地を地図上に描画することがその一番の仕事だからです クライアントが UI 上のボタンなどにより経路選択の振る舞いを他のものと入れ替えれらるよう 現在有効なストラテジーを切り替えるためのメソッドがクラスにはあります

現実世界でのたとえ

各種輸送戦略

空港に到達するための様々な戦略

空港に行くことを想像してみてください バスに乗ることもできまし タクシーを頼むこともできますし 自転車でも行けます これらが 輸送上の戦略です 予算や時間の制限などの要因を勘案して どれを選ぶかを決めます

構造

Strategy デザインパターンの構造Strategy デザインパターンの構造
  1. コンテキスト Context 複数の具象ストラテジーのいずれか一つへの参照を維持し ストラテジー・インターフェースのみを介してこのオブジェクトと通信します

  2. ストラテジー Strategy インターフェースは すべての具象ストラテジーに共通です コンテキストがストラテジーを実行するために使用するメソッドを宣言します

  3. 具象ストラテジー Concrete Strategy コンテキストが使用するアルゴリズムを種々の違う方法で実装します

  4. アルゴリズムの実行が必要となるたびに コンテキストはリンク先ストラテジー・オブジェクトの実行メソッドを呼びます コンテキストは どのような種類のストラテジーを相手に動作するのか どのようにアルゴリズムが実行されるかは認知しません

  5. クライアント Client 特定のストラテジーのオブジェクトを作成し コンテキストに渡します コンテキストは setter を公開しており クライアントは実行時にそれを使ってストラテジーを置き換えます

擬似コード

この例では コンテキストは 様々な算術演算を実行するために複数のストラテジーを使用します

// ストラテジー・インターフェースは、あるアルゴリズムのサポートされている
// すべてのバージョンのアルゴリズムに共通する操作を宣言。コンテキストは、
// 具象ストラテジーで定義されたアルゴリズムを呼ぶのにこのインターフェース
// を使用。
interface Strategy is
    method execute(a, b)

// 具象ストラテジーは、基底ストラテジー・インターフェースに従いながらアル
// ゴリズムを実装する。インターフェースのために、具象ストラテジーはコンテ
// キスト中で交換可能となる。
class ConcreteStrategyAdd implements Strategy is
    method execute(a, b) is
        return a + b

class ConcreteStrategySubtract implements Strategy is
    method execute(a, b) is
        return a - b

class ConcreteStrategyMultiply implements Strategy is
    method execute(a, b) is
        return a * b

// コンテキストは、異なるストラテジーを使って、少し異なるビジネス・ロジッ
// クを実装する。
class Context is
    // コンテキストは、ストラテジー・オブジェクトの一つへの参照を維持する。
    // コンテキストは、ストラテジーの具象クラスを知らない。ストラテジー・
    // インターフェースを通して、全てのストラテジーと共に動作可能なはず。
    private strategy: Strategy

    // 通常コンテキストはコンストラクターを通してストラテジーを受け付ける。
    // そして実行時に切り替えができるよう、setter も提供する。
    method setStrategy(Strategy strategy) is
        this.strategy = strategy

    // コンテキストは、複数個のバージョンのアルゴリズムを自前で持つ代わり
    // に、作業の一部をストラテジー・オブジェクトに委任する。
    method executeStrategy(int a, int b) is
        return strategy.execute(a, b)


// クライアント・コードは具象ストラテジーを一つ選び、コンテキストに渡す。
// クライアントが、正しい選択をするためには、ストラテジー間の違いを知って
// おく必要あり。
class ExampleApplication is
    method main() is
        Create context object.

        Read first number.
        Read last number.
        Read the desired action from user input.

        if (action == addition) then
            context.setStrategy(new ConcreteStrategyAdd())

        if (action == subtraction) then
            context.setStrategy(new ConcreteStrategySubtract())

        if (action == multiplication) then
            context.setStrategy(new ConcreteStrategyMultiply())

        result = context.executeStrategy(First number, Second number)

        Print result.

適応性

オブジェクト内でアルゴリズムのいろいろ異なる変種の使用を望み あるアルゴリズムから別のアルゴリズムに実行時に切り替えられるようにするには Strategy パターンを使用します

Strategy パターンでは オブジェクトの振る舞いを実行時に間接的に切り替えることが可能です それは 仕事の一部を異なる方法で実行するオブジェクトと結びつけることによって行います

一部の振る舞いだけが異なり あとは同じようなクラスがたくさんある場合 Strategy を使用します

Strategy パターンを使用すると 様々な振る舞いを個別のクラス階層に抽出し 元のクラスを一つにまとめ 重複したコードを削減できます

クラスのビジネス・ロジックを ロジック全体からするとあまり重要ではないアルゴリズムの実装の詳細から分離します

Strategy パターンを使用すると コード 内部データ 様々なアルゴリズムの依存関係をコードの残りの部分から分離できます クライアントはアルゴリズムを実行し 実行時にそれらを切り替える単純なインターフェースが使えます

クラスに 同じアルゴリズムの変種を切り替えるための多数の条件文の塊が見受けられる場合 このパターンを使用します

Strategy パターンを適用することにより アルゴリズムを 同一インターフェースを実装する個別のクラスに抽出し 条件文を排除できます 元のオブジェクトは アルゴリズムの多くの変種をその中で実装するのに代わり これらのオブジェクトの一つに実行を委任します

実装方法

  1. コンテキスト・クラス内で 頻繁に変更されそうなアルゴリズムを見つけてください 同じアルゴリズムの変種の切り替えを実行するための条件分の塊がその目印になるかもしれません

  2. アルゴリズムの変種間で共通となるストラテジー・インターフェースを宣言します

  3. アルゴリズムの変種全部を一つずつ個別のクラスに抽出します クラスはストラテジー・インターフェースを実装するようにします

  4. コンテキスト・クラス中に ストラテジー・オブジェクトへの参照を格納するためのフィールドを追加します そのフィールドの値を置き換えるための setter セッターを書き加えてください コンテキストはストラテジー・インターフェースを介してのみストラテジー・オブジェクトを使用する必要があります コンテキストは ストラテジーがデータにアクセスできるようにするためのインターフェースを定義してもかまいません

  5. コンテキストのクライアントは コンテキストの主な任務を実行するのに適切と思われるストラテジーとコンテキストを関連付ける必要があります

長所と短所

  • 実行時にオブジェクト内で使用されるアルゴリズムを交換可能
  • アルゴリズムの実装の詳細を それを使用するコードから分離可能
  • 継承を合成で置き換え可能
  • コンテキストの変更の必要なしに新規ストラテジーを導入可能
  • ごく少数のアルゴリズムしかなく それらがほとんど変更されなければ このパターン導入で必要となる新規クラスやインターフェースでプログラムを必要以上に複雑化させる理由はない
  • クライアントは 適切なストラテジーを選択するにあたり ストラテジー間の違いを認識する必要あり
  • 多くの近代的なプログラミング言語は関数型をサポートしており アルゴリズムの異種を匿名関数の集合中で実装可能 余計なクラスやインターフェースでプログラムを膨らませることなく ストラテジー・オブジェクトとまったく同じようにこれらの関数が使用可能

他のパターンとの関係

  • Bridge State Strategy と限られた意味合いでは Adapter 非常に似た構造をしています 実際のところ これらの全てのパターンは 合成に基づいており 仕事を他のオブジェクトに委任します しかしながら 違う問題を解決します パターンは 単にコードを特定の方法で構造化するためのレシピではありません パターンが解決する問題に関して 開発者同士がするコミュニケーションの道具でもあります

  • CommandStrategy オブジェクトを何らかの操作でパラメーター化できるため 似たように見えます しかし この二つはまったく異なる意図を持っています

    • Command を使用して 任意の操作をオブジェクトに変換できます 操作のパラメーターは そのオブジェクトのフィールドになります 変換により 操作の実行を延期したり キューに入れたり コマンド履歴を保存したり 遠隔サービスにコマンドを送信したりできます

    • 一方 Strategy は通常 同じことを行う異なる方法に関するものです 単一のコンテキスト・クラス内でアルゴリズムを入れ替えることができます

  • Decorator がオブジェクトの表層を変えるのに対し Strategy は中身を変えます

  • Template Method 継承に基づくもので サブクラスでその一部を拡張することによって アルゴリズムを部分的に変更できます Strategy 合成に基き オブジェクトの振る舞いを 新しい振る舞いに関連した異なるストラテジーを与えることにより変更します Template Method クラスのレベルで機能するので 静的です Strategy はオブジェクトのレベルで機能するため 実行時に動作を切り替えることができます

  • State Strategy の拡張と考えることができます どちらのパターンも合成 コンポジション に基づいており コンテキストの振る舞いの変更を ヘルパー・オブジェクトに仕事の一部を委任することにより行います Strategy では これらのオブジェクトは完全に独立しており 互いを意識しません しかし State では 具象状態間の依存関係を制限せず コンテキストの状態を自由に変更できます

コード例

Strategy を C# で Strategy を C++ で Strategy を Go で Strategy を Java で Strategy を PHP で Strategy を Python で Strategy を Ruby で Strategy を Rust で Strategy を Swift で Strategy を TypeScript で