Ich habe einige Zweifel an der folgenden Implementierung des Zustandsmusters:
Ich habe ein Bestellobjekt. Der Einfachheit halber nehmen wir an, dass es eine Menge, eine Produkt-ID, einen Preis und einen Lieferanten hat. Außerdem gibt es eine Reihe bekannter Zustände, in denen die Reihenfolge wechseln kann:
1) Order.isValid () wechselt zwischen Zuständen. Dh, im Zustand können einige Operationen nicht durchgeführt werden. Also, sie sehen aus wie:
void setQuantity (int q) {
if (_state.canChangeQuantity ()) this.quantity = q;
sonst eine Ausnahme auslösen.
}
Ist das richtig, oder sollte ich jeden Staat dazu bringen, die setQuantity-Operation zu implementieren? In diesem Fall, wo wird der Wert gespeichert? In der Reihenfolge, oder der Staat? Im letzteren Fall muss ich die Daten in jedem Zustandsübergang kopieren?
2) orderProcessor.process (order) ist ein Objekt, das order.IsValid überprüft, die Reihenfolge in einen Zustand übergeht, in der Datenbank speichert und einige benutzerdefinierte Aktionen ausführt (in einigen Zuständen wird der Administrator benachrichtigt, in anderen der Client) , etc). Ich habe einen für jeden Staat.
In StateAOrderProcessor wird die Person, die die Bestellung überprüft, per E-Mail benachrichtigt, und die Bestellung wird in den Status b überführt.
Dies drückt jetzt die Statusübergänge außerhalb der Order-Klasse. Das bedeutet, dass Order eine "setState" -Methode hat, so dass jeder Prozessor sie ändern kann. Dieses Ding, um den Zustand von außen zu ändern, klingt nicht nett. Ist das richtig?
3) Eine andere Option besteht darin, die gesamte Validierungslogik auf den Prozessor des jeweiligen Status zu verschieben. Jetzt muss ich jedoch nachverfolgen, wann die Menge einer Order geändert wurde, um festzustellen, ob diese Operation im aktuellen Status gültig ist. Das lässt die Reihenfolge für mich blutarm bleiben.
Was denkst du, Jungs? Können Sie mir einen Rat geben, dieses Ding besser zu entwerfen?
Vielen Dank.
Nick
Dies ist ein ideales Szenario für das Zustandsmuster.
Im State-Muster sollten Ihre State-Klassen für den Statuswechsel verantwortlich sein und nicht nur die Gültigkeit des Übergangs überprüfen. Darüber hinaus ist das Schieben von Zustandsübergängen außerhalb der Ordnungsklasse keine gute Idee und widerspricht dem Muster, aber Sie können immer noch mit einer OrderProcessor-Klasse arbeiten.
Sie sollten jede Statusklasse dazu bringen, die setQuantity-Operation zu implementieren. Die Zustandsklasse sollte alle Methoden implementieren, die in einigen Zuständen gültig sein können, in anderen jedoch nicht, unabhängig davon, ob es sich um eine Zustandsänderung handelt oder nicht.
Es gibt keine Notwendigkeit für Methoden wie canChangeQuantity () und isValid () - die Zustandsklassen stellen sicher, dass sich Ihre Auftragsinstanzen immer in einem gültigen Zustand befinden, da jeder Vorgang, der für den aktuellen Status nicht gültig ist, verworfen wird .
Die Eigenschaften Ihrer Bestellklasse werden mit der Bestellung gespeichert, nicht mit dem Status. In .Net würden Sie dies tun, indem Sie Ihre State-Klassen in die Order-Klasse verschachteln und beim Aufruf einen Verweis auf die Reihenfolge bereitstellen - die State-Klasse hat dann Zugriff auf die privaten Mitglieder des Auftrags. Wenn Sie nicht in .Net arbeiten, müssen Sie einen ähnlichen Mechanismus für Ihre Sprache finden - zum Beispiel Friend-Klassen in C ++.
Ein paar Kommentare zu Ihren Zuständen und Übergängen:
Staat A stellt fest, dass die Bestellung neu ist, die Menge ist & gt; 0 und sie hat eine Produkt-ID. Für mich bedeutet das, dass Sie entweder beide Werte im Konstruktor angeben (um sicherzustellen, dass Ihre Instanz in einem gültigen Zustand startet, aber Sie keine setQuantity-Methode benötigen), oder Sie benötigen einen Anfangszustand mit einem assignProduct (Int32-Menge, Int32 productId) -Methode, die vom Anfangszustand in den Zustand A übergeht.
Ebenso können Sie einen Endstatus für den Übergang vom Status C in Betracht ziehen, nachdem der Lieferant den Preis eingegeben hat.
Wenn Ihr Statusübergang die Zuweisung von zwei Eigenschaften erfordert, sollten Sie eine einzelne Methode in Erwägung ziehen, die beide Eigenschaften nach Parameter akzeptiert (und nicht setQuantity gefolgt von set setProductId), um den Übergang explizit zu machen. p>
Ich würde auch mehr beschreibende Staatsnamen vorschlagen - zum Beispiel, statt StateD, rufen Sie CanceledOrder auf.
Hier ist ein Beispiel, wie ich dieses Muster in C # implementieren würde, ohne neue Zustände hinzuzufügen:
%Vor%Sie können mit Ihren Auftragsprozessorklassen arbeiten, aber sie arbeiten mit den öffentlichen Methoden der Auftragsklasse und lassen die Zustandsklassen der Bestellung alle Verantwortung für den Übergangsstatus übernehmen. Wenn Sie wissen möchten, in welchem Status Sie sich gerade befinden (damit der Auftragsprozessor bestimmen kann, was zu tun ist), können Sie eine String-Status-Eigenschaft für die Auftragsklasse und für BaseState hinzufügen und jede konkrete Statusklasse ihren Namen zurückgeben.
Das Ändern des aktuellen Statusobjekts kann direkt von einem Statusobjekt erfolgen, von der Reihenfolge und sogar OK von einer externen Quelle (Prozessor), obwohl ungewöhnlich.
Entsprechend dem State-Muster delegiert das Order-Objekt alle Anfragen an das aktuelle OrderState-Objekt. Wenn setQuantity () eine zustandsspezifische Operation ist (in Ihrem Beispiel), sollte jedes OrderState-Objekt es implementieren.
Damit das Zustandsmuster funktioniert, muss das Kontextobjekt eine Schnittstelle bereitstellen, die die Zustandsklassen verwenden können. Auf ein Minimum muss dies eine changeState(State)
-Methode enthalten. Dies ist, ich fürchte, nur eine der Grenzen des Musters und ist ein möglicher Grund, warum es nicht immer nützlich ist. Das Geheimnis bei der Verwendung des Zustandsmusters besteht darin, die von den Zuständen benötigte Schnittstelle so klein wie möglich zu halten und auf einen engen Rahmen zu beschränken.
(1) Eine canChangeQuantity
-Methode ist wahrscheinlich besser, als wenn alle States eine setQuantity
implementieren. Wenn einige Staaten etwas komplizierteres tun, als eine Ausnahme auszulösen, folgt dieser Hinweis möglicherweise nicht.
(2) Die Methode setState
ist nicht vermeidbar. Es sollte jedoch so eng wie möglich gehalten werden. In Java wäre dies wahrscheinlich der Paketbereich, in .Net wäre es der Assembly-Bereich (intern).
(3) Der Punkt zur Validierung wirft die Frage auf, wann Sie eine Validierung durchführen. In einigen Fällen ist es sinnvoll, dem Client zu erlauben, Eigenschaften auf ungültige Werte zu setzen und sie nur zu validieren, wenn Sie etwas verarbeiten. In diesem Fall ist jeder Zustand mit einer isValid () -Methode sinnvoll, die den gesamten Kontext validiert. In anderen Fällen möchten Sie einen direkteren Fehler. In diesem Fall würde ich ein isQuantityValid(qty)
und isPriceValid(price)
erstellen, das von den set-Methoden aufgerufen wird, bevor die Werte geändert werden. Wenn sie false zurückgeben, wird eine Ausnahme ausgelöst. Ich habe diese beiden frühen und späten Validierungen immer genannt und es ist nicht einfach zu sagen, was du brauchst, ohne mehr darüber zu wissen, was du vorhast.
Ich würde Informationen in der Order-Klasse speichern und einen Zeiger auf die Order-Instanz an den Status übergeben. Etwas wie das:
%Vor%Haben Sie mehrere verschiedene Klassen, eine pro Staat.
%Vor%und so weiter. Die Idee ist, dass Code, der Befehle in einem bestimmten Zustand benötigt, nur Objekte der richtigen Klasse erhält, so dass keine Zustandsprüfungen erforderlich sind. Code, der nur für Aufträge in jedem beliebigen Status verwendet werden soll, verwendet die BaseClass.
Tags und Links design-patterns state