Vorteile des Strategiemusters

15

Warum ist es vorteilhaft, das Strategiemuster zu verwenden, wenn Sie Ihren Code nur in If / Then-Fällen schreiben können?

Zum Beispiel: Ich habe eine TaxPayer-Klasse und eine ihrer Methoden berechnet die Steuern mithilfe verschiedener Algorithmen. Warum kann es dann keine if / then-Fälle geben und herausfinden, welcher Algorithmus in dieser Methode verwendet werden soll, anstatt das Strategiemuster zu verwenden? Warum können Sie nicht einfach eine separate Methode für jeden Algorithmus in der TaxPayer-Klasse implementieren?

Was bedeutet es auch, dass sich der Algorithmus zur Laufzeit ändert?

Armon Safai
quelle
2
Ist das Hausaufgabe? Wenn ja, ist es besser, dies im Voraus zu sagen.
Fuhrmanator
2
@Fuhrmanator nein, es ist nicht
Armon Safai

Antworten:

20

Zum einen sind große Klumpen von if/elseBlöcken nicht einfach zu testen . Jeder neue "Zweig" fügt einen weiteren Ausführungspfad hinzu und erhöht somit die zyklomatische Komplexität . Wenn Sie Ihren Code gründlich testen möchten, müssen Sie alle Ausführungspfade abdecken, und für jede Bedingung müssen Sie mindestens einen weiteren Test schreiben (vorausgesetzt, Sie schreiben kleine, fokussierte Tests). Auf der anderen Seite stellen Klassen, die Strategien implementieren, normalerweise nur eine öffentliche Methode zur Verfügung, die leicht zu testen ist.

Wenn if/elseSie also geschachtelt sind, werden Sie am Ende viele Tests für einen einzelnen Teil Ihres Codes haben, während Sie mit Strategy wenige Tests für jede der mehreren einfacheren Strategien haben werden. Mit letzterem ist es einfach, eine bessere Abdeckung zu erhalten, da es schwieriger ist, Ausführungspfade zu übersehen.

Wie für Erweiterbarkeit , stellen Sie sich einen Rahmen schreiben, wo die Nutzer sollen der Lage sein , ihr eigenes Verhalten zu injizieren. Beispielsweise möchten Sie eine Art Steuerberechnungsrahmen erstellen und Steuersysteme verschiedener Länder unterstützen. Anstatt sie alle zu implementieren, möchten Sie den Framework-Benutzern lediglich die Möglichkeit geben, eine Implementierung zur Berechnung bestimmter Steuern bereitzustellen.

Hier ist das Strategiemuster:

  • Sie definieren z. B. eine Schnittstelle TaxCalculationund Ihr Framework akzeptiert Instanzen dieses Typs, um Steuern zu berechnen
  • Ein Benutzer des Frameworks erstellt eine Klasse, die diese Schnittstelle implementiert und an Ihr Framework übergibt. Auf diese Weise können Sie einen Teil der Berechnungen durchführen

Sie können nicht dasselbe mit tun if/else, da dies eine Änderung des Code des Frameworks erforderlich machen würde. In diesem Fall wäre es kein Framework mehr. Da Frameworks häufig in kompilierter Form verteilt werden, ist dies möglicherweise die einzige Option.

Selbst wenn Sie nur normalen Code schreiben, ist Strategy von Vorteil, da es Ihre Absichten klarer macht. Es heißt "Diese Logik ist steckbar und bedingt", dh es kann mehrere Implementierungen geben, die je nach Benutzeraktionen, Konfiguration oder sogar Plattform variieren können.

Die Verwendung des Strategy-Musters kann die Lesbarkeit verbessern, da eine Klasse, die eine bestimmte Strategie implementiert, normalerweise einen beschreibenden Namen haben sollte. Beispielsweise USAIncomeTaxCalculatorsind if/elseBlöcke "namenlos", im besten Fall nur kommentiert, und Kommentare können lügen. Außerdem ist es nach meinem persönlichen Geschmack if/elsenicht lesbar , nur mehr als drei Blöcke hintereinander zu haben, und bei verschachtelten Blöcken wird es ziemlich schlimm.

Das Open / Closed-Prinzip ist ebenfalls sehr relevant, da Sie mit Strategy, wie im obigen Beispiel beschrieben, eine Logik in einigen Teilen Ihres Codes erweitern können ("Open for Extension"), ohne diese Teile neu schreiben zu müssen ("Closed for Modification"). ).

scriptin
quelle
1
if/elseblockiert auch die Lesbarkeit des Codes. Als Strategiemuster ist das Open / Closed-Prinzip IMO zu erwähnen.
Maciej Chałapuk,
1
Testbarkeit ist der große Grund. (Die meisten) Jeder Zweig in Ihrem Code sollte getestet werden. Je mehr ifs Sie haben, desto mehr mögliche Pfade existieren durch Ihren Code, desto mehr Tests müssen Sie schreiben und desto mehr Möglichkeiten, dass diese Methode fehlschlägt. Wenn ich den verstorbenen Yogi Berra zitieren darf: "Wenn du zu einer Weggabelung kommst, nimm sie." Dies gilt hervorragend für Unit-Tests. Darüber hinaus ifbedeuten viele Aussagen, dass Sie wahrscheinlich die Logik für diese Bedingungen wiederholen, was Ihre Testlast weiter erhöht und das Risiko des Auftretens von Fehlern erhöht.
Greg Burghardt
Danke für die Antwort. Warum kann ich nicht getrennte Methoden für verschiedene Algorithmen in der gleichen Klasse verwenden?
Armon Safai
Sie können, aber Sie würden immer noch eine Reihe von if/elseBlöcken benötigen , um sie aufzurufen (oder in ihnen, um zu bestimmen, ob sie etwas bewirken sollen oder nicht), daher ist dies keine große Hilfe, außer vielleicht besser lesbarem Code. Und dann auch keine Erweiterbarkeit für Benutzer Ihres hypothetischen Frameworks.
Skript
1
Können Sie klarer sagen, warum das Testen einfacher ist? Das Beispiel für die Umgestaltung einer case-Anweisung (oder wenn / dann) zu einer polymorphen Methode (Grundlage für die Strategie) ist ziemlich einfach zu testen. Wenn ich alle zu testenden Bedingungen kenne, schreibe ich für jede einen Test. Wenn ich Strategien habe, muss ich für jede eine Instanz erstellen und ausführen. Wie ist der Strategieansatz einfacher zu testen? Wir sprechen nicht von komplexen verschachtelten Ifs, wenn Sie die Strategie umgestalten.
Fuhrmanator
5

Warum ist es vorteilhaft, das Strategiemuster zu verwenden, wenn Sie Ihren Code nur in If / Then-Fällen schreiben können?

Manchmal solltest du nur if / then verwenden. Es ist einfacher Code, der leicht zu lesen ist.

Die beiden Hauptprobleme bei einfachem Wenn / Dann-Code sind, dass er das Open-Closed-Prinzip verletzen kann . Wenn Sie jemals eine Bedingung hinzufügen oder ändern müssen, ändern Sie diesen Code. Wenn Sie mehr Bedingungen erwarten, ist das Hinzufügen einer neuen Strategie einfacher, sauberer und weniger anfällig für Brüche.

Das andere Problem ist die Kopplung. Wenn Sie if / then verwenden, sind alle Implementierungen an diese Implementierung gebunden, sodass es in Zukunft schwieriger ist, sie zu ändern. Bei Verwendung der Strategie besteht die einzige Kopplung in der Schnittstelle der Strategie.

Telastyn
quelle
Was ist falsch daran, den Code im if / then-Code zu ändern? Müssten Sie nicht auch den Code im Strategiemuster ändern, wenn Sie sich entscheiden, die Funktionsweise eines der Algorithmen zu ändern?
Armon Safai
@armonsafai - Wenn Sie die Strategie ändern, müssen Sie nur die Strategie testen. Wenn Sie alle Algorithmen ändern , müssen Sie alle Algorithmen testen. Schlimmer noch, wenn Sie eine neue Strategie hinzufügen, müssen Sie nur die Strategie testen. Wenn Sie eine neue Bedingung hinzufügen, müssen Sie alle Bedingungen testen.
Telastyn,
4

Strategie ist nützlich, wenn die if/thenBedingungen auf Typen basieren , wie unter http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html erläutert

Typüberprüfungsbedingungen weisen normalerweise keine hohe zyklomatische Komplexität auf, daher würde ich nicht sagen, dass die Strategie dort die Dinge unbedingt verbessert.

Der Hauptgrund für die Strategie wird in dem GoF-Buch S.316 erläutert, in dem das Muster eingeführt wurde:

Verwenden Sie das Strategiemuster, wenn

...

  • Eine Klasse definiert viele Verhaltensweisen und diese erscheinen als mehrere bedingte Anweisungen in ihren Operationen. Verschieben Sie anstelle vieler Bedingungen verwandte bedingte Zweige in ihre eigene Strategieklasse.

Wie in anderen Antworten erwähnt, können mit dem Strategiemuster bei entsprechender Anwendung neue Erweiterungen (konkrete Strategien) hinzugefügt werden, ohne dass der Rest des Codes geändert werden muss. Dies ist das sogenannte Open-Closed-Prinzip oder Protected Variations-Prinzip . Natürlich müssen Sie die neue konkrete Strategie noch codieren, und der Client-Code muss die Strategien als Plug-Ins erkennen (dies ist nicht trivial).

Bei if/thenBedingungen muss der Code der Klasse geändert werden, die die bedingte Logik enthält. Wie in anderen Antworten erwähnt, ist dies manchmal in Ordnung, wenn Sie nicht die Komplexität hinzufügen möchten, um das Hinzufügen neuer Funktionen (Plug-Ins) ohne erneutes Kompilieren zu unterstützen.

Fuhrmanator
quelle
3

[...] können Sie Ihren Code nur in if / then-Fällen schreiben?

Das ist genau der größte Vorteil des strategischen Musters. Keine Bedingungen.

Sie möchten, dass Ihre Klassen / Methoden / Funktionen so einfach und kurz wie möglich sind. Der Funktionscode ist sehr einfach zu testen und zu lesen.

Die Bedingungen ( if/ elseif/ else) machen Ihre Klassen / Methoden / Funktionen lang, da sich der Code, zu dem eine Entscheidung ausgewertet truewird , normalerweise von dem Code unterscheidet, zu dem die Entscheidung ausgewertet wird false.


Ein weiterer großer Vorteil des Strategiemusters ist, dass es während des gesamten Projekts wiederverwendbar ist.

Wenn Sie das Strategieentwurfsmuster verwenden, ist es sehr wahrscheinlich, dass Sie eine Art IoC-Container haben, aus dem Sie die gewünschte Implementierung einer Schnittstelle erhalten, möglicherweise durch eine getById(int id)Methode, bei der es idsich um ein Enumerator-Mitglied handeln könnte.

Dies bedeutet, dass die Implementierung nur an einer Stelle Ihres Codes erstellt wird.

Wenn Sie weitere Implementierungen hinzufügen möchten, fügen Sie der getByIdMethode die neue Implementierung hinzu, und diese Änderung spiegelt sich überall in dem Code wider, in dem Sie sie aufrufen.

Mit if/ elseif/ elseist dies unmöglich zu tun. Wenn Sie eine neue Implementierung hinzufügen, müssen Sie einen neuen elseifBlock hinzufügen und ihn überall dort ausführen, wo die Implementierungen verwendet wurden. Andernfalls wird möglicherweise ein ungültiger Code angezeigt, da Sie vergessen haben, die Implementierung zu ihrer Struktur hinzuzufügen.


Was bedeutet es auch, dass sich der Algorithmus zur Laufzeit ändert?

In meinem Beispiel idkönnte dies eine Variable sein, die auf der Grundlage einer Benutzereingabe ausgefüllt wird. Wenn der Benutzer auf eine Schaltfläche A klickt, dann id = 2, wenn er auf eine Schaltfläche B klickt, dann id = 8.

Aufgrund des unterschiedlichen idWerts wird eine unterschiedliche Implementierung einer Schnittstelle aus dem IoC-Container abgerufen, und der Code führt unterschiedliche Vorgänge aus.

Andy
quelle
Danke für die Antwort. Warum kann ich nicht getrennte Methoden für verschiedene Algorithmen in der gleichen Klasse verwenden?
Armon Safai
@ArmonSafai Würden getrennte Methoden wirklich etwas lösen? Ich glaube nicht. Sie verschieben das Problem von einem Ort an einen anderen, und die Entscheidung, welche Methode aufgerufen werden soll, basiert auf einem Ergebnis einer Bedingung. Wieder ein if/ elseif/ elseZustand. Gleich wie zuvor, nur an einem anderen Ort.
Andy
Die If / Then-Fälle wären also im Wesentlichen richtig? Müssten Sie if / then-Fälle nicht auch für das Strategiemuster verwenden?
Armon Safai
1
@ArmonSafai Nein, das würdest du nicht. Sie hätten einen Schalter für die idVariable in der getByIdMethode, der die spezifische Implementierung zurückgeben würde. Jedes Mal, wenn Sie eine Implementierung der Schnittstelle benötigen, bitten Sie den IoC-Container, diese an Sie zu liefern.
Andy
1
@ArmonSafai Sie könnten genauso gut eine Methode haben, getSortByEnumType(SortEnum type)die eine Implementierung einer SortSchnittstelle zurückgibt , eine Methode, getSortTypedie eine SortEnumVariable zurückgibt und eine Auflistung als Parameter nimmt, und die getSortByEnumTypeMethode würde wieder einen Schalter für den typeParameter enthalten, der Ihnen den richtigen Sortieralgorithmus zurückgibt. Wenn Sie einen neuen Sortieralgorithmus hinzufügen müssen, müssen Sie nur die Aufzählung und eine Methode bearbeiten. Und du bist bereit.
Andy
2

Warum ist es vorteilhaft, das Strategiemuster zu verwenden, wenn Sie Ihren Code nur in If / Then-Fällen schreiben können?

Mit dem Strategiemuster können Sie Ihre Algorithmen (die Details) von Ihrer Geschäftslogik (übergeordnete Richtlinie) trennen. Diese beiden Dinge sind nicht nur verwirrend zu lesen, wenn sie gemischt sind, sondern haben auch sehr unterschiedliche Gründe, sich zu ändern.

Hier gibt es auch einen wichtigen Faktor für die Skalierbarkeit der Teamarbeit. Stellen Sie sich ein großes Programmierteam vor, in dem viele Leute an diesem Buchhaltungspaket arbeiten. Wenn sich die Steueralgorithmen alle in der TaxPayer- Klasse oder im TaxPayer- Modul befinden, werden Zusammenführungskonflikte wahrscheinlich. Zusammenführungskonflikte sind zeitaufwändig und fehleranfällig. Dieses Mal sinkt die Produktivität des Teams und die durch schlechte Zusammenführung verursachten Fehler beeinträchtigen die Glaubwürdigkeit des Schadens bei den Kunden.

Was bedeutet es auch, dass sich der Algorithmus zur Laufzeit ändert?

Ein Algorithmus, der sich zur Laufzeit ändert, wird durch die Konfiguration oder den Kontext bestimmt. Ein vorhandener Wenn / Dann- Ansatz ermöglicht dies nicht effektiv, da vorhandene aktiv genutzte Klassen neu geladen werden müssen. Mit dem Strategiemuster könnten die Strategieobjekte, die jeden Algorithmus implementieren, bei Verwendung konstruiert werden. Infolgedessen konnten Änderungen an diesen Algorithmen (Fehlerkorrekturen oder Verbesserungen) vorgenommen und zur Laufzeit neu geladen werden. Dieser Ansatz könnte verwendet werden, um eine kontinuierliche Verfügbarkeit und Releases ohne Ausfallzeiten zu ermöglichen.

Alain O'Dea
quelle
1

An sich ist nichts auszusetzen if/else. In vielen Fällen if/elseist dies die einfachste und am besten lesbare Art, Logik auszudrücken. Der von Ihnen beschriebene Ansatz ist daher in vielen Fällen durchaus gültig. (Es ist auch perfekt testbar, sodass dies kein Problem darstellt.)

Es gibt jedoch einige besondere Fälle, in denen ein Strategiemuster die Wartbarkeit des gesamten Codes verbessern kann. Beispielsweise:

  • Wenn sich die bestimmten Steuerberechnungsalgorithmen unabhängig voneinander und von der Kernlogik ändern können. In diesem Fall wäre es schön, sie in verschiedene Klassen zu unterteilen, da Änderungen lokalisiert werden.
  • Wenn in Zukunft neue Algorithmen hinzugefügt werden können, ohne dass sich die Kernlogik ändert.
  • Wenn sich die Ursache für den Unterschied zwischen den beiden Algorithmen auch auf andere Teile des Codes auswirkt. Angenommen, Sie wählen zwischen den beiden Algorithmen basierend auf der Einkommensklasse des Steuerzahlers. Wenn diese Einkommensklasse auch dazu führt, dass Sie andere Zweige an anderen Stellen im Code auswählen, ist es sinnvoller, eine Strategie, die der Einkommensklasse entspricht, einmal zu instanziieren und bei Bedarf aufzurufen, anstatt mehrere if / else-Zweige über den Code zu verteilen.

Damit das Strategiemuster Sinn macht, sollte die Schnittstelle zwischen der Kernlogik und den Steuerberechnungsalgorithmen stabiler sein als die einzelnen Komponenten. Wenn es genauso wahrscheinlich ist, dass eine Anforderungsänderung zu einer Änderung der Benutzeroberfläche führt, ist das Strategiemuster möglicherweise tatsächlich eine Verbindlichkeit.

Es kommt alles darauf an, ob die "Steuerberechnungsalgorithmen" sauber von der Kernlogik, die sie aufruft, getrennt werden können. Ein Strategiemuster hat im Vergleich zu einem einen gewissen Overhead if/else, sodass Sie von Fall zu Fall entscheiden müssen, ob sich die Investition lohnt.

JacquesB
quelle