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?
Antworten:
Zum einen sind große Klumpen von
if/else
Blö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/else
Sie 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:
TaxCalculation
und Ihr Framework akzeptiert Instanzen dieses Typs, um Steuern zu berechnenSie 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
USAIncomeTaxCalculator
sindif/else
Blöcke "namenlos", im besten Fall nur kommentiert, und Kommentare können lügen. Außerdem ist es nach meinem persönlichen Geschmackif/else
nicht 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"). ).
quelle
if/else
blockiert auch die Lesbarkeit des Codes. Als Strategiemuster ist das Open / Closed-Prinzip IMO zu erwähnen.if
s 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 hinausif
bedeuten 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.if/else
Blö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.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.
quelle
Strategie ist nützlich, wenn die
if/then
Bedingungen auf Typen basieren , wie unter http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html erläutertTypü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:
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/then
Bedingungen 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.quelle
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 ausgewertettrue
wird , normalerweise von dem Code unterscheidet, zu dem die Entscheidung ausgewertet wirdfalse
.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 esid
sich 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
getById
Methode die neue Implementierung hinzu, und diese Änderung spiegelt sich überall in dem Code wider, in dem Sie sie aufrufen.Mit
if
/elseif
/else
ist dies unmöglich zu tun. Wenn Sie eine neue Implementierung hinzufügen, müssen Sie einen neuenelseif
Block 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.In meinem Beispiel
id
könnte dies eine Variable sein, die auf der Grundlage einer Benutzereingabe ausgefüllt wird. Wenn der Benutzer auf eine Schaltfläche A klickt, dannid = 2
, wenn er auf eine Schaltfläche B klickt, dannid = 8
.Aufgrund des unterschiedlichen
id
Werts wird eine unterschiedliche Implementierung einer Schnittstelle aus dem IoC-Container abgerufen, und der Code führt unterschiedliche Vorgänge aus.quelle
if
/elseif
/else
Zustand. Gleich wie zuvor, nur an einem anderen Ort.id
Variable in dergetById
Methode, 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.getSortByEnumType(SortEnum type)
die eine Implementierung einerSort
Schnittstelle zurückgibt , eine Methode,getSortType
die eineSortEnum
Variable zurückgibt und eine Auflistung als Parameter nimmt, und diegetSortByEnumType
Methode würde wieder einen Schalter für dentype
Parameter 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.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.
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.
quelle
An sich ist nichts auszusetzen
if/else
. In vielen Fällenif/else
ist 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:
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.quelle