Besseres Verständnis des Entwurfsmusters "Strategie"

8

Ich habe mich eine Weile für Designmuster interessiert und angefangen, "Head First Design Patterns" zu lesen. Ich begann mit dem ersten Muster, das als "Strategie" -Muster bezeichnet wurde. Ich ging das in den folgenden Bildern dargestellte Problem durch und versuchte zunächst, selbst eine Lösung vorzuschlagen, damit ich die Bedeutung des Musters wirklich erfassen konnte.

Meine Frage ist also, warum meine Lösung für das unten stehende Problem nicht gut genug ist. Was sind die guten / schlechten Punkte meiner Lösung gegenüber dem Muster? Was macht das Muster eindeutig zur einzig praktikablen Lösung?

MEINE LÖSUNG

Elternklasse: ENTE

<?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/>");
  }
 }


}
?>

Vererbungsklasse: MallardDuck

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

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

Vererbungsklasse: 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/>";
 }
}
Imran Omar Bukhsh
quelle
Ihre Lösung ist nicht genau das, was auf dem Buch gemeint ist (oder sogar eine Lösung für die eine)
Mauris
können Sie helfen, besser zu erklären
Imran Omar Bukhsh
5
Ich bin mir nicht sicher, ob Sie Seiten aus Head First Design-Mustern scannen und einfach ins Web stellen können.
Ladislav Mrnka
1
Dies sieht aus wie eine Codeüberprüfung .
Steven Jeuris
2
@ Imran Wie bei Amazon erwähnt, sind diese Seiten urheberrechtlich geschütztes Material. Sie können hier nicht frei gepostet werden. Bitte bearbeiten Sie die Frage, um das Problem in Ihren eigenen Worten anzugeben.
Adam Lear

Antworten:

6

Ihr Code wird kaputt gehen, wenn z. B. Enten mit anderen Geräuschen quaken. Der Boolesche Wert führt nur dazu, dass andere Boolesche Werte zu wirklich haarigen if-Anweisungen führen.

Das Strategiemuster ist jedoch einfach. Nehmen Sie in diesem Fall die Quacksalbermethode und platzieren Sie sie in einer eigenen Klasse oder Schnittstelle. Eine Schnittstelle in PHP wäre eine Klasse, die keine andere Implementierung als Methoden enthält, die Stubs sind (dh nichts passiert, wenn Sie sie aufrufen).

class Quackable {
    function quack() {}
}

Auf diese Weise können Sie mehrere quackbare Implementierungen erstellen:

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

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

Und weil wir es strategisch gemacht haben, können wir weitere Arten von "Quacksalber" hinzufügen:

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

Gleiches gilt für Flug- und Schwimmmethoden für die Entenklasse. Die Art und Weise, wie Sie eine Duck-Klasse mit einem Quackable implementieren, sieht folgendermaßen aus (die Quackable-Schnittstelle wird über den Konstruktor bereitgestellt, so funktioniert die Abhängigkeitsinjektion):

class Duck {
    protected $quackable;

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

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

Die Implementierung der Stockente und der Lockente ist einfach, da Sie nur angeben müssen, welche Art von Quacksalber die Ente tun soll:

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
    }
}

Alles zu benutzen ist einfach:

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

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

Ich hoffe, das alles macht für Sie Sinn.

Spoike
quelle
Der Code kann Fehler haben, es ist eine Weile her, seit ich etwas in PHP codiert habe. :-P
Spoike
Außerdem muss das OP noch einige Seiten umblättern, um zum Strategiemuster und zum Prinzip "Zusammensetzung vor Vererbung" in diesem Buch zu gelangen. :) Die gescannten Seiten haben das Problem der Vererbung.
Spoike
Danke für die Erklärung
Imran Omar Bukhsh
4

Erstens hat Ihr Problem nichts mit dem Strategiemuster zu tun. Die Idee des Strategiemusters besteht darin, die Verantwortung für ein bestimmtes Verhalten in eine andere Klasse einzubeziehen. Auf diese Weise können Sie zur Laufzeit verschiedene Verhaltensweisen in eine Instanz einbinden.

Jetzt funktioniert Ihre Lösung für das Szenario, in dem Sie sich befinden, recht gut, aber das Szenario ist nur eine Übung, die weit entfernt von Problemen der realen Welt ist.
Wenn Sie diese Technik verwenden, um große Projekte anzugehen, besteht die Gefahr, dass Sie 5-10 Vererbungsebenen erhalten, bei denen jede Unterklasse mit immer mehr Flags gekoppelt ist. Ein solcher Code ist äußerst fragil und ausführlich. Das Herumspielen mit dem internen Zustand aller Superklassen ist nicht gerade die OOP-Methode, um mit solchen Dingen umzugehen, da es die Trennung von Bedenken verwischt.

Eine Lösung mit Schnittstellen ist wesentlich sauberer, robuster und wird sich daher im Laufe der Zeit als wartbarer erweisen. Versuchen Sie nicht, eine allgemeine Allzweck-Basisente herzustellen, sondern machen Sie klare Abstraktionen der verschiedenen Aspekte verschiedener Enten. Ich habe kürzlich einen Blog-Beitrag zu diesem Thema verfasst, den Sie vielleicht hilfreich finden.

back2dos
quelle
1
+1, aber ich muss sagen, dass dies das Beispiel für das Strategiemuster im Buch ist, das ein ausgezeichnetes Buch ist. Leider hat er auf den nächsten Seiten nicht gezeigt, wo das Verhalten von Fliegen, Quacksalbern usw. entworfen und dann in die Enten eingesteckt wird (dh den Teil, der das Strategiemuster auf das Problem anwendet), aber es steht im Buch.
Alb
Ich habe die nächste Seite des Buches hinzugefügt, um zu zeigen, warum die Schnittstellenlösung insgesamt nein nein ist!
Imran Omar Bukhsh
@ Alb: Ja, es ist ungefähr 13 Seiten
Imran Omar Bukhsh
@ Imran: Sorry, aber der Autor des Buches ist einfach falsch. Schnittstellen sagen nichts über die Implementierung aus. Die Klasse kann durch Komposition oder Delegation implementiert werden (das Strategiemuster ist ein Sonderfall). Es ist jedoch ein schlechtes Design, einer Ente, die nicht fliegen kann, eine Flugmethode zu geben, nur weil man sie sonst nicht lösen kann. Und es skaliert nicht. Was ist, wenn Sie auch haben RuberBall? Wird das RuberDuckauch eine leere Rollmethode haben?
back2dos
3

Ah, gute Frage. Ich denke nicht, dass es sehr empfehlenswert ist, Mitgliedsvariablen zu haben, die die Funktionalität umschalten. Das Problem bei dieser Lösung besteht hauptsächlich darin, dass Sie, da Sie die Methoden flyund quackfür die DuckKlasse verfügbar machen, anscheinend sagen, dass "Alle Enten können fly/ quack". Was schlimmer ist , dass in Abhängigkeit von der Laufzeittyp der Ente Instanz i haben, flyoder quackkann oder auch gar nichts. Dies kann zu sehr verwirrendem Code führen.

Wenn Sie im Beispiel des Buches eine Ente erwarten, die fliegen kann, können Sie eingeben (oder testen, da Sie eine dynamische Sprache verwenden), ob es sich um eine FlyableEnte handelt.

Wenn ich jedes Mal, wenn ich eine Ente hatte, bevor ich sie anrufe, quackmuss ich entscheiden, ob die fliegende Mitgliedsvariable auf true gesetzt ist oder nicht, was zu einer Menge Codeduplizierung führen würde.

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

Die FlyableSchnittstelle hilft, Erwartungen zu etablieren. Ja, Ihre Standardmethode quackprüft, ob dies der Fall sein sollte oder nicht, aber ich als Anrufer habe keine Garantie dafür, dass das Anrufen sogar sicher ist quack. Denken Sie auch an das Szenario mit dem, RubberDuckydas eher quietschen als quaken sollte. Wenn Sie die quackMethode überschreiben , müssen Sie wissen, um die $quackableMitgliedsvariable zu überprüfen , bevor Sie Maßnahmen ergreifen.

Etwas anderes, das meiner Meinung nach diese Lösung unklar macht, ist, dass es schwierig ist, Erwartungen an Eingabetypen zu setzen, da Sie eine dynamische Sprache wie PHP verwenden. (Nicht, dass daran etwas falsch ist, aber eine statisch typisierte Sprache könnte das Verständnis erleichtern.)

Ich hoffe das hilft und macht Sinn.

Chiffre
quelle
1

Ihr Code wird schnell unordentlich, wenn Sie 10 verschiedene Arten von Enten haben, die alle unterschiedliche Kombinationen von beispielsweise 3 verschiedenen Varianten von Fliegen, Quaken und Schwimmen haben.

Persönlich stellte ich fest, dass der wahre Wert des Strategiemusters klar wurde, als ich anfing, Komponententests zu schreiben und Abhängigkeitsinjektion zu verwenden. Wenn Sie es nicht verwenden, haben Sie große Probleme beim Verwalten von Abhängigkeiten und beim Erstellen von Objekten in Ihren Tests.

Sie werden in Situationen geraten, in denen das Erstellen einer Ente kompliziert wird. Dann möchten Sie möglicherweise einen Komponententest für einige der Quacksalber- oder Flugmethoden schreiben, aber Sie können dies nicht tun, ohne in Ihrem Test eine Ente zu erstellen.

Alb
quelle
1

Ein boolescher Zustand zum Speichern, ob Enten quaken / fliegen können, ist eine sehr spezifische Lösung für das spezielle Problem, mit dem Sie in Ihrem Klassendesign konfrontiert sind, und kann möglicherweise nicht auf andere Fälle angewendet werden, in denen das Strategiemuster angemessen ist.

Ich denke, Ihr "quackbarer" Ansatz macht den Code etwas komplizierter. Es ist eine Sache mehr, die Sie und Benutzer Ihrer Klasse beachten müssen, als wenn Sie ein Strategiemuster verwenden würden.

Da Ihr Beispielcode keine Beispiele dafür zeigt, wie Sie die Methoden "Quacksalber" und "Fliegen" überschreiben würden, bemerken Sie keinen großen Vorteil der Verwendung des Strategiemusters - Reduzierung der Codeduplizierung. Was würden Sie bei einem Dutzend verschiedener Entenklassen tun, die untereinander drei verschiedene "Fliegen" -Verhalten haben? Wenn Sie Ihren Ansatz verwenden, werden Sie wahrscheinlich feststellen, dass Sie den duplizierten Code ausschneiden und einfügen und dann möglicherweise in statische "Hilfs" -Klassen extrahieren. Das Strategiemuster ist viel sauberer.

Richeym
quelle
1

Eine schöne visuelle Demo sagt immer mehr als tausend Worte.

John Lindquist macht einen großartigen Job, um Screencasts über verschiedene Designmuster aufzunehmen. Sie können seine 50 Cent über dieses bestimmte Muster (und vieles mehr) auf seinem Blog finden

Pierre Watelet
quelle