Zum Beispiel:
Es können nur Bewerbungen aktualisiert werden, die noch nicht geprüft oder genehmigt wurden. Mit anderen Worten, eine Person kann ihr Job-Appliance-Formular aktualisieren, bis die Personalabteilung mit der Überprüfung beginnt oder es bereits akzeptiert wurde.
Eine Bewerbung kann also in 4 Zuständen erfolgen:
APPLIED (Ausgangszustand), IN_REVIEW, APPROVED, DECLINED
Wie erreiche ich ein solches Verhalten?
Sicherlich kann ich eine update () -Methode in die Anwendungsklasse schreiben, den Anwendungsstatus überprüfen und nichts tun oder eine Ausnahme auslösen, wenn sich die Anwendung nicht im erforderlichen Status befindet
Diese Art von Code macht jedoch nicht deutlich, dass eine solche Regel existiert. Sie ermöglicht es jedem, die update () -Methode aufzurufen, und erst wenn ein Client fehlschlägt, weiß er, dass eine solche Operation nicht zulässig war. Daher muss sich der Kunde bewusst sein, dass ein solcher Versuch fehlschlagen kann. Seien Sie daher vorsichtig. Wenn der Kunde sich solcher Dinge bewusst ist, bedeutet dies auch, dass die Logik nach außen leckt.
Ich habe versucht, für jeden Status unterschiedliche Klassen zu erstellen (ApprovedApplication usw.) und zulässige Operationen nur für zulässige Klassen durchzuführen, aber diese Art von Ansatz fühlt sich auch falsch an.
Gibt es ein offizielles Entwurfsmuster oder einen einfachen Code, um ein solches Verhalten zu implementieren?
this kind of code does not make it obvious such a rule exists
- Deshalb hat Code Dokumentation. Autoren von gutem Code werden den Rat von Euphoric befolgen und eine Methode bereitstellen, mit der die Regel von außen getestet werden kann, bevor die Hardware ausprobiert wird.Antworten:
Diese Art von Situation taucht ziemlich oft auf. Beispielsweise können Dateien nur im geöffneten Zustand bearbeitet werden. Wenn Sie versuchen, nach dem Schließen etwas mit einer Datei zu tun, wird eine Laufzeitausnahme angezeigt.
Ihr Wunsch ( in Ihrer vorherigen Frage zum Ausdruck gebracht ), das Typensystem der Sprache zu verwenden, um sicherzustellen, dass nicht einmal das Falsche passieren kann, ist nobel, da Fehler bei der Kompilierung immer Laufzeitfehlern vorzuziehen sind. Es gibt jedoch kein Entwurfsmuster, das ich für diese Art von Situation kenne, wahrscheinlich weil es mehr Probleme verursachen als lösen würde. (Es wäre unpraktisch.)
Das nächste, was Ihrer mir bekannten Situation am nächsten kommt, ist die Modellierung verschiedener Zustände eines Objekts, die unterschiedlichen Funktionen entsprechen, über zusätzliche Schnittstellen. Auf diese Weise reduzieren Sie jedoch nur die Anzahl der Stellen im Code, an denen möglicherweise ein Laufzeitfehler auftritt Die Möglichkeit eines Laufzeitfehlers wird nicht beseitigt.
In Ihrer Situation würden Sie also eine Reihe von Schnittstellen deklarieren, die beschreiben, was mit Ihrem Objekt in seinen verschiedenen Zuständen getan werden kann, und Ihr Objekt würde bei einem Zustandsübergang einen Verweis auf die richtige Schnittstelle zurückgeben.
So würde beispielsweise die
approve()
Methode Ihrer Klasse eineApprovedApplication
Schnittstelle zurückgeben. Die Schnittstelle würde privat implementiert (über eine verschachtelte Klasse), sodass Code, der nur einen Verweis auf eine hatApplication
, keine derApprovedApplication
Methoden aufrufen kann . Dann gibt Code, der eine genehmigte Anwendung manipuliert, ausdrücklich seine Absicht an, dies zum Zeitpunkt der Kompilierung zu tun, indem er eineApprovedApplication
Arbeit benötigt. Wenn Sie diese Schnittstelle jedoch irgendwo speichern und diese Schnittstelle nach demdecline()
Aufrufen der Methode weiter verwenden, wird natürlich immer noch ein Laufzeitfehler angezeigt. Ich glaube nicht, dass es eine perfekte Lösung für Ihr Problem gibt.quelle
if( someone.hasApprovalPermission( application ) ) { application.approve(); }
das Prinzip der Trennung von Bedenken gibt an, dass weder Anwendung, noch jemand, sollten Entscheidungen bezüglich der Berechtigungen und Sicherheit befassen.Ich nicke mit dem Kopf über verschiedene Teile der verschiedenen Antworten, aber das OP scheint immer noch das Problem der Flusskontrolle zu haben. Es gibt zu viel zu versuchen, in Worten zu verschmelzen. Ich werde nur einen Code korrigieren - The State Pattern.
Staatsnamen als Vergangenheitsform
"In_Review" ist vielleicht kein Zustand, sondern ein Übergang oder Prozess. Andernfalls sollten Ihre Statusnamen konsistent sein: "Anwenden", "Genehmigen", "Ablehnen" usw. ODER Sie haben auch "Überprüft". Oder nicht.
Der angewendete Status führt einen Überprüfungsübergang durch und setzt den Status auf Überprüft. Der überprüfte Status führt einen Genehmigungsübergang durch und setzt den Status auf Genehmigt (oder Abgelehnt).
Bearbeiten - Fehlerbehandlung Kommentare
Ein aktueller Kommentar:
Und von der ursprünglichen Frage:
Es gibt noch mehr Designarbeit zu erledigen. Es gibt keine
Unified Field Theory Pattern
. Die Verwirrung ergibt sich aus der Annahme, dass das Zustandsübergangs-Framework allgemeine Anwendungsfunktionen und Fehlerbehandlung übernimmt. Das fühlt sich falsch an, weil es so ist. Die angezeigte Antwort dient zur Steuerung der Zustandsänderung.Dies deutet darauf hin, dass hier drei Funktionen funktionieren: Der Status, die Aktualisierung und die Interaktion der beiden. In diesem Fall
Application
ist nicht der Code, den ich geschrieben habe. Es kann es verwenden, um den aktuellen Status zu bestimmen.Application
ist auch nicht dasapplicationPaperwork
.Application
ist nicht das Zusammenspiel der beiden, sondern könnte eine allgemeineStateContextEvaluator
Klasse sein. JetztApplication
werden diese Komponenteninteraktionen orchestriert und dann entsprechend gehandelt, wie wenn eine Fehlermeldung ausgegeben wird.Bearbeiten beenden
quelle
Application
Konstruktor, in dem die Ausnahme ausgelöst wird. Möglicherweise führt ein AnrufAppliedState.Approve()
zu einer Benutzermeldung: "Der Antrag muss überprüft werden, bevor er genehmigt werden kann."AppliedState.apply()
, den Benutzer sanft daran zu erinnern, dass der Antrag bereits eingereicht wurde und auf eine Überprüfung wartet. Und das Programm geht weiter.Im Allgemeinen beschreiben Sie einen Workflow. Insbesondere fallen Geschäftsfunktionen, die von Staaten wie REVIEWED APPROVED oder DECLINED verkörpert werden, unter die Überschrift "Geschäftsregeln" oder "Geschäftslogik".
Um klar zu sein, sollten Geschäftsregeln nicht in Ausnahmen kodiert werden. Dies würde bedeuten, Ausnahmen für die Programmflusssteuerung zu verwenden, und es gibt viele gute Gründe, warum Sie dies nicht tun sollten. Ausnahmen sollten für außergewöhnliche Bedingungen verwendet werden, und der UNGÜLTIGE Status einer Anwendung ist aus geschäftlicher Sicht völlig außergewöhnlich.
Verwenden Sie Ausnahmen in Fällen, in denen das Programm ohne Benutzereingriff keine Fehlerbedingung beheben kann (z. B. "Datei nicht gefunden").
Es gibt kein spezifisches Muster zum Schreiben von Geschäftslogik, außer den üblichen Techniken zum Anordnen von Geschäftsdatenverarbeitungssystemen und zum Schreiben von Code zur Implementierung Ihrer Prozesse. Wenn die Geschäftsregeln und der Workflow ausgefeilt sind, sollten Sie eine Art Workflow-Server oder eine Geschäftsregel-Engine verwenden.
In jedem Fall können die Zustände REVIEW, APPROVED, DECLINED usw. durch eine private Variable vom Typ Enum in Ihrer Klasse dargestellt werden. Wenn Sie Getter / Setter-Methoden verwenden, können Sie steuern, ob die Setter Änderungen zulassen, indem Sie zuerst den Wert der Enum-Variablen untersuchen. Wenn jemand versucht, einen Setter zu schreiben , wenn der ENUM - Wert im falschen Zustand ist, dann können Sie eine Ausnahme werfen.
quelle
Application
könnte eine Schnittstelle sein, und Sie könnten eine Implementierung für jeden der Zustände haben. Die Schnittstelle könnte einemoveToNextState()
Methode haben, die die gesamte Workflow-Logik verbirgt.Für die Bedürfnisse des Kunden könnte es auch eine Methode geben, die direkt zurückgibt, was Sie tun können und nicht (dh eine Reihe von Booleschen Werten), anstatt nur den Status, sodass Sie keine "Checkliste" im Kunden benötigen (ich nehme an der Client soll sowieso ein MVC-Controller oder eine Benutzeroberfläche sein).
Anstatt jedoch eine Ausnahme auszulösen, können Sie einfach nichts tun und den Versuch protokollieren. Dies ist zur Laufzeit sicher, Regeln wurden durchgesetzt und der Client hatte Möglichkeiten, die "Update" -Kontrollen auszublenden.
quelle
Ein Ansatz für dieses Problem, der in freier Wildbahn äußerst erfolgreich war, ist Hypermedia - die Darstellung des Zustands der Entität wird von Hypermedia-Steuerelementen begleitet, die die Arten von Übergängen beschreiben, die derzeit zulässig sind. Der Verbraucher fragt die Steuerelemente ab, um herauszufinden, was getan werden kann.
Es handelt sich um eine Zustandsmaschine mit einer Abfrage in der Benutzeroberfläche, mit der Sie ermitteln können, welche Ereignisse Sie auslösen dürfen.
Mit anderen Worten: Wir beschreiben das Web (REST).
Ein anderer Ansatz besteht darin, sich eine Vorstellung von verschiedenen Schnittstellen für verschiedene Zustände zu machen und eine Abfrage bereitzustellen, mit der Sie erkennen können, welche Schnittstellen derzeit verfügbar sind. Denken Sie an IUnknown :: QueryInterface oder Downcasting. Der Client-Code spielt Mutter Mai I mit dem Staat, um herauszufinden, was erlaubt ist.
Es ist im Wesentlichen das gleiche Muster - nur eine Schnittstelle zur Darstellung der Hypermedia-Steuerelemente.
quelle
Hier ist ein Beispiel dafür, wie Sie dies aus funktionaler Sicht angehen und wie Sie potenzielle Fallstricke vermeiden können. Ich arbeite in Haskell, von dem ich annehme, dass Sie es nicht wissen, also werde ich es im weiteren Verlauf ausführlich erklären.
Dies definiert einen Datentyp, der sich in einem von vier Zuständen befinden kann, die Ihren Anwendungsstatus entsprechen.
ApplicationDetails
wird als vorhandener Typ angenommen, der die detaillierten Informationen enthält.Ein Typalias, der explizit von und nach konvertiert werden muss
Application
. Dies bedeutet, dass, wenn wir die folgende Funktion definieren, die eine akzeptiert und auspacktUpdatableApplication
und etwas Nützliches damit macht,Dann müssen wir die Anwendung explizit in eine aktualisierbare Anwendung konvertieren, bevor wir sie verwenden können. Dies geschieht mit folgender Funktion:
Hier machen wir drei interessante Dinge:
UpdatableApplication
(die nur eine Anmerkung zum Kompilierungstyp der hinzugefügten Typänderung enthält, da Haskell über eine spezielle Funktion verfügt, um diese Art von Tricks auf Typebene auszuführen, die zur Laufzeit nichts kostet). , undOption
in C # oderOptional
in Java - es ist ein Objekt, das ein Ergebnis umschließt, das möglicherweise fehlt).Um dies tatsächlich zusammenzustellen, müssen wir diese Funktion aufrufen und, wenn das Ergebnis erfolgreich ist, an die Aktualisierungsfunktion weiterleiten ...
Da die
updateApplication
Funktion das umschlossene Objekt benötigt, können wir nicht vergessen, die Voraussetzungen zu überprüfen. Und da die Vorbedingungsprüfungsfunktion das umschlossene Objekt in einemMaybe
Objekt zurückgibt, können wir nicht vergessen, das Ergebnis zu überprüfen und entsprechend zu reagieren, wenn es fehlschlägt.Nun ... Sie könnten dies in einer objektorientierten Sprache tun. Aber es ist weniger bequem:
Maybe
sie normalerweise keine so bequeme Möglichkeit, die Daten zu extrahieren und gleichzeitig den Pfad zu wählen. Auch hier ist der Mustervergleich sehr nützlich.quelle
Sie können das Muster «Befehl» verwenden und dann den Invoker bitten, eine Liste der gültigen Funktionen entsprechend dem Status der Empfängerklasse bereitzustellen.
Ich habe dasselbe verwendet, um Funktionen für verschiedene Schnittstellen bereitzustellen, die meinen Code aufrufen sollten. Einige der Optionen waren je nach aktuellem Status des Datensatzes nicht verfügbar. Daher hat mein Aufrufer die Liste aktualisiert und auf diese Weise hat jede GUI den Aufrufer gefragt welche Optionen zur Verfügung standen und sie malten sich entsprechend.
quelle