「戦略」デザインパターンの理解を深める


8

しばらくデザインパターンに興味があり、「Head First Design Patterns」を読み始めました。最初は「戦略」パターンと呼ばれるパターンから始めました。下の画像で概説されている問題を調べ、最初に自分で解決策を提案しようとしたので、パターンの重要性を本当に理解できました。

だから私の質問は、以下の問題に対する私の解決策がなぜ十分ではないということです。パターンに対するソリューションの良い点と悪い点は何ですか?パターンが明らかに唯一の実行可能なソリューションになるのはなぜですか?

私の解決策

親クラス:DUCK

<?php
class Duck
{
 public  $swimmable;
 public  $quackable;
 public  $flyable;

 function display()
 {
  echo "A Duck Looks Like This<BR/>";
 }

 function  quack()
 {
  if($this->quackable==1)
  {
   echo("Quack<BR/>");
  }
 }

 function swim()
 {
  if($this->swimmable==1)
  {
   echo("Swim<BR/>");
  }
 }

 function  fly()
 {
  if($this->flyable==1)
  {
   echo("Fly<BR/>");
  }
 }


}
?>

継承クラス:マガモ

<?php
class MallardDuck extends Duck
{
 function MallardDuck()
 {
  $this->quackable = 1;
  $this->swimmable = 1;
 }

 function display()
 {
  echo "A Mallard Duck Looks Like This<BR/>";
 }
}
?>

継承クラス:WoddenDecoyDuck

<?php
class WoddenDecoyDuck extends Duck
{
 function woddendecoyduck()
 {
  $this->quackable = 0;
  $this->swimmable = 0;
 }

 function display()
 {
  echo "A Wooden Decoy Duck Looks Like This<BR/>";
 }
}

あなたの解決策は、本で何を意味するのか(またはその解決策でさえも)は
異なります

わかりやすく説明できますか
イムランオマールブクシュ2011年

5
Head First DesignパターンからページをスキャンしてWebに配置できるかどうかはわかりません。
Ladislav Mrnka、2011


2
@Imranアマゾンで言及されているように、それらのページは著作権で保護された資料です。ここに自由に投稿することはできません。質問を編集して、問題をあなた自身の言葉で述べてください。
アダムリア

回答:


6

コードが壊れるのは、たとえば、アヒルが異なる音をたたくときです。ブール値は、他のブール値を本当に毛深いifステートメントに導くだけです。

ただし、戦略パターンは単純です。このインスタンスでquackメソッドを受け取り、それを独自のクラスまたはインターフェースに配置します。phpのインターフェースは、スタブであるメソッド以外の実装を含まないクラスです(つまり、それらを呼び出しても何も起こりません)。

class Quackable {
    function quack() {}
}

そうすれば、いくつかの疑わしい実装を作成できます。

class DuckQuack extends Quackable {
    function quack() {
        return "Quack!";
    }
}

class SilentQuack extends Quackable {
    function quack() {
        return ""; 
            // The decoy duck can't quack. It is silent.
    }
}

また、戦略パターンを賢明に実行したため、より多くの種類の「いんちき」を追加できます。

class DoubleQuack extends Quackable {
    function quack() {
        return "Quackety-quack!"
    }
}

同じことがDuckクラスのflyメソッドとswimメソッドに適用できます。quackableを使用してDuckクラスを実装する方法は次のようになります(quackableインターフェースは、依存関係注入がどのように機能するかを示すコンストラクタを通じて提供されます)。

class Duck {
    protected $quackable;

    // The constructor
    function __construct($quackable) {
        $this->quackable = quackable;
    }

    function quack() {
        echo $this->quackable->quack();
    }
}

マガモとおとりのアヒルの実装は簡単です。これは、アヒルが行うべきクワクの種類を指定するだけでよいためです。

class MallardDuck extends Duck {
    function __construct() {
        parent::__construct(new DuckQuack());
           // we construct the mallard duck with a duck quack
    }
}

class DecoyDuck extends Duck {
    function __construct() {
        parent::__construct(new SilentQuack());
           // we construct the decoy duck with a silent quack
    }
}

それをすべて使用するのは簡単です:

$duck1 = new MallardDuck();
$duck2 = new DecoyDuck();

$duck1.quack(); // echoes out "Quack!"
$duck2.quack(); // echoes out "" (because decoy ducks don't quack)

これがすべてあなたにとって理にかなっているといいのですが。


コードにエラーがある可能性があります。phpで何かをコーディングしてから久しぶりです。:-P
スポーク

その上、OPは、戦略パターンとその本の「継承前の構成」の原則に到達するために、さらに数ページをめくる必要があります。:)スキャンされたページは、継承の問題の前置きになります。
スポーク、2011年

説明
ありがとう

4

まず、問題は戦略パターンとは何の関係もありません。戦略パターンの考え方は、特定の動作の責任を別のクラスに分解することです。これにより、実行時にさまざまな動作を1つのインスタンスにプラグインできます。

さて、あなたのソリューションはあなたがいるシナリオに対して合理的にうまく機能しますが、シナリオは単なる演習であり、現実の問題とはかなりかけ離れています。
この手法を使用して大規模なプロジェクトに取り組む場合、継承の5〜10層に巻き込まれるリスクがあり、各サブクラスはますます多くのフラグに結合されます。このようなコードは非常に壊れやすく、冗長です。すべてのスーパークラスの内部状態をいじるのは、そのようなことを処理するOOPの方法とは異なります。それは、懸念の分離を不明瞭にするためです。

インターフェースを使用したソリューションは、かなりクリーンで堅牢であり、長期的に維持できることが証明されます。一般的な汎用のアヒルを作るのではなく、さまざまなアヒルのさまざまな側面を明確に抽象化してください。最近、この件についてブログに投稿しました。


1
+1しかし、私はこれが優れた本である本の戦略パターンに与えられた例であることを言わなければなりません。残念ながら、彼は、ハエ、クワガタなどの動作が設計され、アヒル(つまり、問題に戦略パターンを適用する部分)にプラグインされる次のページを示していませんが、それは本にあります。
Alb

この本の次のページを追加して、インターフェイスソリューションがなぜまったくないのかを示しました。
Imran Omar Bukhsh

@Alb:はい、約13ページ
Imran Omar Bukhsh '29

@イムラン:申し訳ありませんが、本の作者は明らかに間違っています。インターフェースは実装について何も言いません。クラスは、合成または委任を介して実装できます(戦略パターンは特殊なケースです)。しかし、飛ぶことができないアヒルに飛ぶ方法を与えることは、あなたがそれを解決できないという理由だけで、そうでなければ設計が貧弱です。そして、それはスケーリングしません。あなたも持っている場合はどうなりますRuberBallか?にRuberDuckも空のロール方式がありますか?
back2dos

3

ああ、いい質問です。機能を切り替えるメンバー変数を用意することはあまり良い習慣ではないと思います。主にこのソリューションの問題は、クラスのflyおよびquackメソッドを公​​開しているためDuck、「すべてのアヒルがfly/ できる」と言っているように見えることですquack。いただきまし悪いことは、私が持っている、アヒルのインスタンスのランタイム型に応じて、それであるflyか、quackあるいは何もしない場合があります。これにより、コードが非常に混乱する可能性があります。

この本の例では、飛ぶことができるアヒルを期待しているときに、それがアヒルだった場合は入力(または動的言語を使用しているのでテスト)ができますFlyable

アヒルがいるたびに、呼び出す前にquack、フライ可能メンバー変数がtrueに設定されているかどうかを確認する必要があります。これにより、コードの重複が多数発生します。

if($duck->quackable) $duck->quack();

Flyableインターフェイスは期待を確立することができます。はい、あなたのデフォルトのquackメソッドはそれが必要かどうかをチェックしますが、私は、呼び出し元として、それが呼び出しても安全であるという保証はありませんquack。また、RubberDuckyどちらががたがたよりもきしむべきシナリオについて考えてください。quackメソッドをオーバーライドするときは$quackable、アクションを実行する前にメンバー変数を確認する必要があります。

PHPのような動的言語を使用しているため、この解決策が不明確になると私が思う別のことは、入力タイプに関する期待を設定するのを難しくすることです。(これに問題があるわけではありませんが、静的に型付けされた言語の方が理解しやすいかもしれません。)

これがお役に立てば幸いです。


1

フライ、クワッ、スイムの3種類のバリエーションの組み合わせがすべて異なる10種類のアヒルがある場合、コードはすぐに乱雑になります。

個人的には、単体テストの作成と依存性注入の使用を開始したときに、戦略パターンの真の価値が明らかになったことがわかりました。これを使用しないと、依存関係の管理に多くの問題が生じ、テストでオブジェクトを作成することになります。

アヒルを作成するのが複雑になる状況に到達し、次に、いんちきや飛行のメソッドの単体テストを記述したい場合がありますが、テストでアヒルを作成せずに行うことはできません。


1

アヒルがガタガタ飛ぶことができるかどうかを保存するブール状態を持つことは、クラス設計で直面している特定の問題に対する非常に具体的な解決策であり、戦略パターンが適切である他のケースには適用できない場合があります。

私はあなたの「疑わしい」アプローチがコードをもう少し複雑にしていると思います。これはもう1つ重要なことであり、クラスのユーザーは、戦略パターンを使用する場合に必要となる以上のことを認識しておく必要があります。

最後に、サンプルコードでは「quack」メソッドと「fly」メソッドをオーバーライドする方法の例を示していないため、戦略パターンを使用することの大きな利点に気付かず、コードの重複を削減できます。12種類の鴨のクラスがあり、それらの中で3種類の「ハエ」の振る舞いがある場合、どうしますか?あなたのアプローチを使用すると、おそらく切り取って貼り付け、そしておそらく重複したコードを静的な「ヘルパー」クラスに抽出することに気付くでしょう。戦略パターンははるかにきれいです。


1

素晴らしいビジュアルデモは、常に1000語に値します。

John Lindquistは、さまざまなデザインパターンに関するスクリーンキャストを記録するために素晴らしい仕事をしています。彼のブログで、この特定のパターン(およびその他の多く)について彼の50セントを見つけることができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.