Ich habe mir Java-Code zum Anschauen gegeben, der ein Autorennen simuliert, zu dem auch die Implementierung einer Basis-Zustandsmaschine gehört. Dies ist keine klassische Informatik-Zustandsmaschine, sondern lediglich ein Objekt, das mehrere Zustände haben kann und auf der Grundlage einer Reihe von Berechnungen zwischen seinen Zuständen wechseln kann.
Um nur das Problem zu beschreiben, habe ich eine Auto-Klasse mit einer verschachtelten Enum-Klasse, die einige Konstanten für den Zustand des Autos definiert (wie AUS, LEERLAUF, ANTRIEB, RÜCKWÄRTS usw.). Innerhalb derselben Fahrzeugklasse habe ich eine Aktualisierungsfunktion, die im Wesentlichen aus einer großen switch-Anweisung besteht, die den aktuellen Status des Fahrzeugs einschaltet, einige Berechnungen durchführt und dann den Fahrzeugstatus ändert.
Soweit ich sehen kann, wird der Cars-Status nur innerhalb seiner eigenen Klasse verwendet.
Meine Frage ist, ist dies der beste Weg, um mit der Implementierung einer Zustandsmaschine der oben beschriebenen Art umzugehen? Es klingt nach der naheliegendsten Lösung, aber in der Vergangenheit habe ich immer gehört, dass "switch-Anweisungen schlecht sind".
Das Hauptproblem, das ich hier sehen kann, ist, dass die switch-Anweisung möglicherweise sehr groß werden kann, wenn wir weitere Zustände hinzufügen (falls dies als notwendig erachtet wird) und der Code unhandlich und schwer zu warten sein kann.
Was wäre eine bessere Lösung für dieses Problem?
quelle
object.state = object.function(object.state);
Antworten:
Ich habe das Auto mit State Pattern in eine Art Staatsmaschine verwandelt . Beachten Sie , dass für die Statusauswahl keine
switch
oderif-then-else
Anweisungen verwendet werden.In diesen Fällen sind alle Zustände innere Klassen, aber es könnte anders implementiert werden.
Jeder Status enthält die gültigen Status, in die er geändert werden kann.
Der Benutzer wird aufgefordert, den nächsten Status einzugeben, falls mehr als einer möglich ist, oder einfach zu bestätigen, falls nur einer möglich ist.
Sie können es kompilieren und ausführen, um es zu testen.
Ich habe ein grafisches Dialogfeld verwendet, weil es auf diese Weise einfacher war, es interaktiv in Eclipse auszuführen.
Das UML-Diagramm stammt von hier .
quelle
Es ist diese Art der Übervereinfachung, die der objektorientierten Programmierung einen schlechten Ruf verleiht. Die Verwendung
if
ist genauso "schlecht" wie die Verwendung einer switch-Anweisung. In beiden Fällen versenden Sie nicht polymorph.Wenn Sie eine Regel haben müssen, die in einen gesunden Biss passt, versuchen Sie diese:
Eine switch-Anweisung, die an keiner anderen Stelle in der Codebasis dupliziert wird, kann manchmal dazu führen, dass sie nicht böse ist. Wenn die Fälle nicht öffentlich sind, sondern gekapselt sind, geht es niemanden etwas an. Vor allem, wenn Sie wissen, wie und wann Sie es in Klassen umgestalten müssen. Nur weil du kannst, heißt das nicht, dass du musst. Es ist, weil Sie können, dass es weniger kritisch ist, es jetzt zu tun.
Wenn Sie versuchen, mehr und mehr Dinge in die switch-Anweisung zu schieben, das Wissen über Fälle zu verbreiten oder sich wünschen, es wäre nicht so schlimm, nur eine Kopie davon zu erstellen, ist es Zeit, die Fälle in separate Klassen umzugestalten.
Wenn Sie Zeit haben, mehr als ein paar Soundbits über das Refactoring von switch-Anweisungen zu lesen, hat c2 eine sehr ausgewogene Seite über den Geruch von switch-Anweisungen .
Selbst im OOP-Code ist nicht jeder Switch schlecht. So verwenden Sie es und warum.
quelle
Das Auto ist eine Art Zustandsmaschine. Switch-Anweisungen sind der einfachste Weg, eine Zustandsmaschine ohne Super- und Unterzustände zu implementieren.
quelle
Switch-Anweisungen sind nicht schlecht. Hören Sie nicht auf Leute, die Dinge wie "Schalteraussagen sind schlecht" sagen! Einige spezielle Verwendungen von switch-Anweisungen sind Antimuster, z. B. die Verwendung von switch zum Emulieren von Unterklassen. (Aber Sie können dieses Antimuster auch mit if's implementieren, also denke ich, wenn's auch schlecht sind!).
Ihre Implementierung klingt gut. Sie haben Recht, es wird schwierig zu pflegen, wenn Sie viele weitere Zustände hinzufügen. Dies ist jedoch nicht nur eine Frage der Implementierung - ein Objekt mit vielen Zuständen mit unterschiedlichem Verhalten ist selbst ein Problem. Stellen Sie sich vor, Ihr Auto hat 25 Zustände, in denen jeder ein anderes Verhalten und unterschiedliche Regeln für Zustandsübergänge aufweist. Nur dieses Verhalten zu spezifizieren und zu dokumentieren, wäre eine enorme Aufgabe. Sie werden Tausende von Regeln für den Staatsübergang haben! Die Größe des
switch
wäre nur ein Symptom für ein größeres Problem. Vermeiden Sie es also, wenn möglich, diesen Weg zu gehen.Eine mögliche Abhilfe besteht darin, den Staat in unabhängige Unterzustände aufzuteilen. Ist REVERSE beispielsweise wirklich ein anderer Zustand als DRIVE? Möglicherweise könnten die Fahrzeugzustände in zwei Teile unterteilt werden: Motorzustand (AUS, LEERLAUF, ANTRIEB) und Richtung (VORWÄRTS, RÜCKWÄRTS). Der Motorstatus und die Motorrichtung sind wahrscheinlich weitgehend unabhängig voneinander, sodass Sie die Regeln für die Logikduplizierung und den Zustandsübergang reduzieren. Mehr Objekte mit weniger Status sind viel einfacher zu verwalten als ein einzelnes Objekt mit zahlreichen Status.
quelle
In Ihrem Beispiel sind Autos einfach Zustandsmaschinen im klassischen Sinne der Informatik. Sie haben eine kleine, genau definierte Menge von Zuständen und eine Art Zustandsübergangslogik.
Mein erster Vorschlag ist, die Übergangslogik in eine eigene Funktion (oder Klasse, wenn Ihre Sprache keine erstklassigen Funktionen unterstützt) aufzuteilen.
Mein zweiter Vorschlag ist, die Übergangslogik in den Zustand selbst aufzuteilen, der eine eigene Funktion haben würde (oder eine Klasse, wenn Ihre Sprache keine erstklassigen Funktionen unterstützt).
In beiden Schemata würde der Prozess zum Übergang des Zustands ungefähr so aussehen:
oder
Der zweite könnte natürlich trivial in die Autoklasse eingewickelt werden, um wie der erste auszusehen.
In beiden Szenarien würde das Hinzufügen eines neuen Status (z. B. ENTWURF) nur das Hinzufügen eines neuen Typs von Statusobjekten und das Ändern der Objekte umfassen, die speziell in den neuen Status wechseln.
quelle
Es hängt davon ab, wie groß die sein
switch
könnte.In Ihrem Beispiel denke ich, dass a
switch
in Ordnung ist, da ich mir keinen anderen Zustand vorstellen kann, den Sie habenCar
könnten, sodass er mit der Zeit nicht größer wird.Wenn das einzige Problem darin besteht, einen großen Switch zu haben, in dem jeder
case
viele Anweisungen enthält, erstellen Sie einfach unterschiedliche private Methoden für jeden.Manchmal schlagen die Leute das Zustandsdesignmuster vor , aber es ist angemessener, wenn Sie sich mit komplexer Logik befassen und Staaten unterschiedliche Geschäftsentscheidungen für viele verschiedene Vorgänge treffen. Ansonsten sollten einfache Probleme einfache Lösungen haben.
In einigen Szenarien können Methoden vorhanden sein, die nur Aufgaben ausführen, wenn der Status A oder B, nicht jedoch C oder D ist, oder mehrere Methoden mit sehr einfachen Operationen, die vom Status abhängen. Dann
switch
wären eine oder mehrere Aussagen besser.quelle
Dies klingt wie eine Zustandsmaschine der alten Schule, wie sie verwendet wurde, bevor jemand objektorientierte Programmierung durchführte, geschweige denn Entwurfsmuster. Es kann in jeder Sprache implementiert werden, die switch-Anweisungen enthält, wie z. B. C.
Wie andere gesagt haben, ist an switch-Anweisungen nichts von Natur aus falsch. Die Alternativen sind oft komplizierter und schwerer zu verstehen.
Wenn die Anzahl der Switch-Fälle nicht lächerlich groß wird, kann das Ding ziemlich überschaubar bleiben. Der erste Schritt, um es lesbar zu halten, besteht darin, den Code jeweils durch einen Funktionsaufruf zu ersetzen, um das Verhalten des Zustands zu implementieren.
quelle