見出し画像

🛠️「戦術家」Strategyパターン と「架け橋」Bridgeパターンは形がおなじ

切り替えのStrategy(戦術) 分離のBridge(架け橋)


Strategy パターンの UML クラス図は Bridge パターンのものと同じである。しかし、これら二つのデザインパターンはその意図が同じではない。

なんだかだまされた気になるデザインパターン

Strategy は合成 (composition)

Strategy パターンでは、合成 (composition) を用いる。Strategy パターンにおける振る舞いは別々のインターフェイスと、これらのインターフェイスを実装した抽象クラスとして定義される。具体的なクラスは、これらのインターフェイスをカプセル化する。これにより、振る舞いと、それを用いるクラスがうまく分離できる。

Strategyパターン

アルゴリズムを別のStrategyクラスにカプセル化することで、アルゴリズムをコンテキストから独立して変化させることができ、切り替え、理解、拡張が容易になる。

ストラテジーで利用される、そもそものカプセル化の考え方の元になる、メイヤーの開放/閉鎖原則 原理主義

1988年に、バートランド・メイヤーが『オブジェクト指向ソフトウェアの構築(Object Oriented Software Construction)』の中で「開放/閉鎖原則」という語を生み出したと一般に称される。それは、一度完成したら、クラスの実装はエラーの修正のためだけに変更され、新機能や機能変更には別のクラスを作る必要があるというものだった。

https://amzn.to/3B3MV1Y

「OOSC」と呼ばれる本書は、ソフトウェア工学の主要な問題に対する答えとしてオブジェクト技術を提示しており、特にソフトウェアの品質要素である「正しさ」「堅牢性」「拡張性」「再利用性」への対応に重点を置いています。

オブジェクト指向プログラミングには、「グローバル変数を使ってはならない」「インスタンス変数はすべてprivateにする」などのさまざまなノウハウや豊富なデザインパターンがあり、開放/閉鎖原則はそれらの根底にあるといわれる

抽象化の川を超え、GoFのBridgeはウィンドウの実装分離・拡張で活用

処理系により実装が変わるケースがあるとする、さらにその処理系はやりたい抽象化とも関係ない、たが程よく抽象化したいとき、橋渡しが必要になる

Bridgeの例 implに注目


  • 実装が抽象化されるので途中で書き換えられる

  • 実装が隠せる

ブリッジ、こういう時使おう

・実装が複数あって正直迷っている(処理系が違うとか)
・利用側に色んな意味で負担をかけたくない(余計な心配をかけたくない、とか)

function run() {
   var screen = new Screen();
   var audio = new Audio();
   var hand = new Gestures(screen);
   var mouse = new Mouse(audio);
   hand.tap();
   hand.swipe();
   hand.pinch();
   mouse.click();
   mouse.move();
   mouse.wheel();
   log.show();
}

これがブリッジの例。スクリーンのジェスチャーをハンドがやり、オーディオをマウス操作するためにオブジェクトを橋渡ししている。ここではブリッジは構造。

ファクトリとの併用

Window/WindowImp の例では、プラットフォーム固有の情報をカプセル化することを唯一の任務とするファクトリー オブジェクト(Abstract Factory (87) を参照)を導入することができます。Window はファクトリに WindowImp を要求し、ファクトリは適切な種類の WindowImp を返します。このアプローチの利点は、AbstractionがImplementorクラスと直接結合していないことです。

Strategyに戻ってみる。

Strategyは改行についてのお説教が始まる。振る舞い=改行、構造=ウィンドウ。

function run() {
   var package = { from: "76712", to: "10012", weigth: "lkg" };
   // the 3 strategies
   var ups = new UPS();
   var usps = new USPS();
   var fedex = new Fedex();
   var shipping = new Shipping();
   shipping.setStrategy(ups);
   log.add("UPS Strategy: " + shipping.calculate(package));
   shipping.setStrategy(usps);
   log.add("USPS Strategy: " + shipping.calculate(package));
   shipping.setStrategy(fedex);
   log.add("Fedex Strategy: " + shipping.calculate(package));
   log.show();
}

ストラテジの切り替え(ups,usps,fedex)が行われているのがわかる。例えがわかりづらいが、クロネコ、サガワ、ウーバーとかって分かれてると思えばよいか。

一般にコンテキストとStrategyとの結合は、Bridge パターンにおける抽象化と実装の結合より強固である。

時系列においてストラテジがその名の通り柔軟に戦略を変えていくために予め強い結合を求めるのとは対照的に、構造においてブリッジが二つのオブジェクトクラスを橋渡しする。

ブリッジにはimplいう接尾辞がついたが、出自についてはゆっくり見よう。


数式で見るストラテジパターン

ストラテジーパターンは、アルゴリズムをオブジェクトとしてカプセル化し、アルゴリズムを使用するクライアントとは独立にしてアルゴリズムを切り替え可能にするパターンです。

A=f(C,S)

ここで、Cはコンテキスト(アルゴリズムを使用する環境)、Sは選択された戦略(アルゴリズム)、Aはアルゴリズムの実行結果、fは選択された戦略に基づいてアルゴリズムを実行する関数です。

Dot

digraph StrategyPattern {
    node [shape=record]

    Context [label="{Context|+executeStrategy(): void\n+setStrategy(Strategy): void}"]
    Strategy [label="{Strategy|+algorithmInterface(): void}"]
    ConcreteStrategyA [label="{ConcreteStrategyA|+algorithmInterface(): void}"]
    ConcreteStrategyB [label="{ConcreteStrategyB|+algorithmInterface(): void}"]
    ConcreteStrategyC [label="{ConcreteStrategyC|+algorithmInterface(): void}"]

    Strategy -> ConcreteStrategyA [arrowhead="none"]
    Strategy -> ConcreteStrategyB [arrowhead="none"]
    Strategy -> ConcreteStrategyC [arrowhead="none"]
    Context -> Strategy [label="uses" dir="back"]

    {rank=same; ConcreteStrategyA ConcreteStrategyB ConcreteStrategyC}
}


いいなと思ったら応援しよう!

あたり帳簿
お願い致します