Dieses Bild stammt aus Anwenden domänengesteuerter Designs und Muster: Mit Beispielen in C # und .NET
Dies ist das Klassendiagramm für das Zustandsmuster, in dem a SalesOrder
während seiner Lebensdauer verschiedene Zustände haben kann. Es sind nur bestimmte Übergänge zwischen den verschiedenen Zuständen zulässig.
Jetzt ist die OrderState
Klasse eine abstract
Klasse und alle ihre Methoden werden an ihre Unterklassen vererbt. Wenn wir die Unterklasse betrachten, Cancelled
die ein Endzustand ist, der keine Übergänge zu anderen Zuständen zulässt, müssen wir override
alle Methoden in dieser Klasse verwenden, um Ausnahmen auszulösen.
Verstößt das nicht gegen Liskovs Substitutionsprinzip, da eine Unterklasse das Verhalten der Eltern nicht ändern sollte? Behebt das Ändern der abstrakten Klasse in eine Schnittstelle dieses Problem?
Wie kann das behoben werden?
cancel()
ändert den Status in abgebrochen.Antworten:
Diese besondere Implementierung, ja. Wenn Sie die Zustände zu konkreten Klassen machen und nicht zu abstrakten Implementierern, werden Sie davon abkommen.
Das Zustandsmuster, auf das Sie sich beziehen, ist jedoch im Allgemeinen etwas, mit dem ich nicht einverstanden bin, weil ich gesehen habe, wie es wächst. Ich denke, es gibt genügend Gründe, dies als Verstoß gegen das Prinzip der Einzelverantwortung zu bezeichnen, da diese Zustandsverwaltungsmuster letztendlich zentrale Aufbewahrungsorte für die Kenntnis des aktuellen Zustands vieler anderer Teile eines Systems sind. Diese zentralisierte Zustandsverwaltung erfordert häufig Geschäftsregeln, die für viele verschiedene Teile des Systems relevant sind, um sie angemessen zu koordinieren.
Stellen Sie sich vor, alle Teile des Systems, die sich um den Zustand kümmern, befänden sich in unterschiedlichen Diensten, unterschiedlichen Prozessen auf unterschiedlichen Maschinen, ein zentraler Statusmanager, der den Status für jeden dieser Bereiche detailliert angibt, führt effektiv zu Engpässen im gesamten verteilten System, und ich halte den Engpass für ein Zeichen einer SRP-Verletzung sowie allgemein schlechtes Design.
Im Gegensatz dazu würde ich vorschlagen, Objekte intelligenter zu gestalten als das Modellobjekt im MVC-Muster, bei dem das Modell weiß, wie es sich selbst behandelt, und keinen externen Orchestrator für die Verwaltung seiner internen Abläufe oder Gründe benötigt.
Selbst wenn Sie ein Statusmuster wie dieses in ein Objekt einfügen, damit es nur von selbst verwaltet wird, haben Sie das Gefühl, dass Sie dieses Objekt zu groß machen würden. Workflows sollten durch die Komposition verschiedener selbstverantwortlicher Objekte erfolgen, anstatt mit einem einzelnen orchestrierten Zustand, der den Fluss anderer Objekte oder den Fluss der Intelligenz in sich selbst verwaltet.
Aber an diesem Punkt ist es mehr Kunst als Engineering- und so ist es auf jeden Fall Ihren Ansatz dieser Dinge einig subjektiv ist, die besagten , die Prinzipien sind eine gute Anleitung und ja die Implementierung von Liste ist eine LSP Verletzung, kann aber nicht sein korrigiert werden. Seien Sie nur sehr vorsichtig mit der SRP, wenn Sie ein Muster dieser Art verwenden, und Sie sind wahrscheinlich sicher.
quelle
Das ist eine häufige Fehlinterpretation von LSP. Eine Unterklasse kann das Verhalten des übergeordneten Elements ändern, solange es dem übergeordneten Elementtyp entspricht.
Es gibt eine lange Erklärung auf Wikipedia , die die Dinge vorschlägt, die gegen LSP verstoßen:
Persönlich fällt es mir leichter, mich daran zu erinnern: Wenn ich einen Parameter in einer Methode vom Typ A betrachte, würde mich jemand überraschen, der einen Subtyp B übergibt? Andernfalls liegt ein Verstoß gegen LSP vor.
Ist es eine Überraschung, eine Exception zu werfen? Nicht wirklich. Dies kann jederzeit passieren, unabhängig davon, ob ich die Ship-Methode bei OrderState oder Granted oder Shipped aufrufe. Also muss ich das erklären und es ist nicht wirklich ein Verstoß gegen LSP.
Trotzdem denke ich, dass es bessere Möglichkeiten gibt, mit dieser Situation umzugehen. Wenn ich dies in C # schreiben würde, würde ich Interfaces verwenden und nach der Implementierung eines Interfaces suchen, bevor ich die Methode aufrufe. Wenn der aktuelle OrderState beispielsweise IShippable nicht implementiert, rufen Sie keine Ship-Methode auf.
Aber dann würde ich auch nicht das Zustandsmuster für diese spezielle Situation verwenden. Das Statusmuster ist dem Status einer Anwendung viel angemessener als dem Status eines solchen Domänenobjekts.
Kurz gesagt, dies ist ein schlecht erfundenes Beispiel für das Zustandsmuster und keine besonders gute Möglichkeit, mit dem Zustand eines Auftrags umzugehen. Aber es verletzt wohl nicht LSP. Und das staatliche Muster an und für sich tut es mit Sicherheit nicht.
quelle
CircleWithFixedCenterButMutableRadius
als wahrscheinliche LSP-Verletzung betrachten, wenn der Verbrauchervertrag fürImmutablePoint
spezifiziert, dassX.equals(Y)
Verbraucher X frei für Y und umgekehrt ersetzen können, und daher davon absehen müssen, die Klasse so zu verwenden, dass eine solche Substitution Probleme verursachen würde. Der Typ kann möglicherweise legitim definierenequals
, dass alle Instanzen als eindeutig verglichen werden, aber ein solches Verhalten würde wahrscheinlich seine Nützlichkeit einschränken.(Dies ist aus Sicht von C # geschrieben, also keine geprüften Ausnahmen.)
Laut Wikipedia-Artikel über LSP ist eine der Bedingungen von LSP:
Wie solltest du das verstehen? Was genau sind "Ausnahmen, die von den Methoden des Supertyps ausgelöst werden", wenn die Methoden des Supertyps abstrakt sind? Ich denke, das sind die Ausnahmen, die als die Ausnahmen dokumentiert sind, die durch die Methoden des Supertyps ausgelöst werden können.
Was dies bedeutet ist, dass, wenn
OrderState.Ship()
dokumentiert ist als etwas "wirft,InvalidOperationException
wenn diese Operationen nicht vom aktuellen Status unterstützt werden", dann denke ich, dass dieses Design LSP nicht kaputt macht. Wenn andererseits die Supertyp-Methoden nicht auf diese Weise dokumentiert werden, wird der LSP verletzt.Dies bedeutet jedoch nicht, dass dies ein gutes Design ist. Sie sollten keine Ausnahmen für den normalen Kontrollfluss verwenden, dem dies sehr nahe zu kommen scheint. In einer realen Anwendung möchten Sie wahrscheinlich auch wissen, ob eine Operation ausgeführt werden kann, bevor Sie dies versuchen, z. B. um die Schaltfläche "Versenden" in der Benutzeroberfläche zu deaktivieren.
quelle