Hi,
ich befasse mich gerade mit Zustandsautomaten, siehe Artikel
http://www.mikrocontroller.net/articles/Statemachine
Angehangen der Code für eine ganz simple "Ampel" (wurscht was das ist),
bei der man die Ampel anschalten kann, die Rot-Phase länger ist, man
immer die Ampel ausschalten kann, und man diese auch blinken lassen
kann, mit 3 Tasten gesteuert (Pollin Eval Board, so nebenbei).
Meine Transitionen hier sind, wie gesagt, bereits durch 3 Tasten
gesteuert, im Gegensatz zu dem Link oben ist das in der Anwendung also
schon ein bisschen ifs mit nots und ands... ich erwarte aber noch
wesentlich mehr, denn ich möchte zum Beispiel von Zustand 3 in Zustand 4
wechseln, wenn alle Bedingungen der Transition auf Zustand 3 weiterhin
erfüllt sind. Beispiel: Eine Kette von Schaltern. Wenn einer auf aus
geht, oder man den Notausschalter betätigt, dann soll zum Beispiel eine
Abschaltungskette erfolgen. Mein Kumpel lötet mir noch eine Testplatine
mit zum Beispiel 8 Leds und 8 Schaltern.
Ich kann ja auch beliebig verzweigen, also eine einfache Verzweigung ist
eben die Unterscheidung zwischen Not-Aus-Schalter und weiterer
Eskalation in spätere Zustände.
Ich sehe irgendwie nicht, wie mir eine Tabelle wie in dem Link helfen
könnte, vielleicht checke ich das auch einfach nicht, denn bei dem Link
oben ist ja bereits ein IF in der Tabellenabarbeitung.
Nun grübel ich gerade, wie ich meinen Code a) lesbar halte und b) nicht
aufblähen will in eine Unmenge von Fallunterscheidungen in einem
Zustandsautomaten.
Ich grübel über eine Tabelle, die für jeden Zustand einen Pointer auf
eine Transitionsfunktion hat. Diese gibt dann den nächsen Zustand
zurück.
Diese Funktionen sind natürlich auch alles andere als angenehm, da dort
dann natürlich die ganze Logik stattfinden muss. Allerdings kann man
dann jedem Zustand seine eindeutige Transition zuweisen...
Habt ihr einen Tipp diesbezüglich? Ein einfaches Codesample, damit ich
das besser verstehe? Danke!
LG
Jens
Was mir dazu als Anregung einfällt (falls du C++ benutzen kannst wird
das einfacher): Du könntest jeden Zustand durch eine Klasse darstellen,
die eine Methode notify() hat, mit welcher du sie über eine Änderung des
Zustands der Taster informierst. Jede dieser Klassen könnte dann aus
notify() den neuen Zustand zurückgeben, und ihre eigene Logik in dieser
Methode implementieren. Der aktuelle Zuständ wäre eine Instanz einer
dieser Klassen. Dann spart man sich das riesige Switch und hat die Logik
für die einzelnen Zustände ganz gut getrennt. Wenn man dann noch mehrere
ähnliche Zustände in einer Klasse grupiert, vermeidet man auch,
ähnlichen Code mehrmals zu kopieren.
Ich hab' das noch nie so umgesetzt, aber es hört sich so an, als ob es
funktionieren könnte. ;)
Grüße,
Sven
Danke, Sven.
Habe auch schon über C++ nachgedacht, ich möchte aber vermutlich erst
einmal C durchziehen, mal schauen... da fühle ich mich irgendwie noch
bissi sicherer als bei C++. Wenn der Code ausartet, dann denke ich,
nehme ich lieber Objektorientierung.
Ich probiere jetzt mal Pointer auf Funktionen in einer Matrix, mal
schauen was das für ein Konstrukt ergibt... falls das überhaupt Sinn
macht, ist für mich in der Praxis absolutes Neuland!
LG
Jens
Hört sich für mich an, als ob es im Endeffekt ungefähr auf dasselbe
Konzept rausläuft wie mein Vorschlag -- bloß unübersichtlicher, weil man
ein objektorientiertes Konzept ohne Objekte umsetzt ;)
Aber ich verstehe, dass du es erstmal mit C versuchen willst. Da bin ich
allerdings nicht so der Experte (ich denke eher objektorientiert),
deshalb kann ich da keine guten Tipps geben.
Hey Sven,
könntest Du denn ein Codesample aus dem Ärmel schütteln, damit ich Deine
Konzeptidee mal ausprobieren kann? Wäre super! Kannst Dir ja mal meine
"Ampel" anschauen. Für mich klingt das auch total logisch, das jeder
Zustand (Instanz) intrinsisch seine eigene Logik hat, wenn ich das
richtig verstehe. Aber ich habe noch nicht viel in C++ gemacht, da sehe
ich im Moment überhaupt nicht, wie die Zustände übergehen. Gibt es dann
Konstrutoren und Destruktoren? Null Peilung. Wenn Du Bock und die Zeit
hast, kannst ja mal ein Snippet schicken...
LG
Jens
Hey,
ok, ich schreibe mal was auf, wie ich mir das vorstellen könnte, für ein
ganz einfaches Beispiel.
Das sieht evtl. total over-engineered aus, was es auch ist für dieses
einfache Beispiel, aber ich denke es ist relativ übersichtlich und
leicht erweiterbar. Nur die Speicherverwaltung ist relativ mäßig (es
verliert auch irgendwo 8 Bytes, die finde ich aber grad nicht).
Das System hat einfach nur vier Zustände, Grün, Gelb und Rot, und einen
"Not-Aus-Zustand", der an ist, solange ein Not-Aus-Button gedrückt ist.
Lässt man den Button wieder los, kehrt das System in den Zustand zurück,
in dem es vorher war.
An deiner Stelle würde ich das Programm mal mit einem Debugger
durchsteppen, dann sieht man leicht, was es tut.
ampel.h:
1
#ifndef FOOBAR_H
2
#define FOOBAR_H
3
4
#include<iostream>
5
6
// Ein Ereignis was passiert, z.B. "Button gedrückt"
7
structEvent{
8
enumButtonState{
9
// das sei mal der Knopf, der zum nächsten Zustand der Ampel umschaltet
wow, danke Sven, das werde ich mir mal genauer anschauen! Ich merke
schon, DU KANNST das aus dem Ärmel schütteln!
Ich jammer dann bei Gelegenheit mit Fragen ;)
LG,
Jens
Sicher, wenn's Probleme gibt frag' einfach. Ganz überzeugt bin ich nicht
davon, dass das das optimale Design ist... aber ich denke, angenehmer
als ein riesiges switch { } ist es auf jeden Fall ;)
Sven B. schrieb:> // Edit: Ah, ja, im Destruktor von System müsste man currentState> löschen, das sind die 8 Bytes. Ist aber wurscht.
Hast du in letzter Zeit Java oder C# programmiert?
Du new-st mir da in deinem Code entschieden viel zu viel. Und das meiste
davon sind unsinnige new.
1
intmain()
2
{
3
System*myMachine=newSystem;
4
5
...
6
deletemyMachine;
7
}
wozu dynamisch anlegen?
1
intmain()
2
{
3
SystemmyMachine;
4
5
...
6
}
und gut ists.
Das hier
1
voidSystem::notify(constEvent*event)
2
{
3
....
4
deleteevent;
5
}
ist ein absolutes No_no. Wozu soll die Funktion auch das Event Objekt
löschen. Das geht diese Funktion nichts an, wo der Event herkommt! Mach
aus dem Pointer eine Referenz ...
1
voidSystem::notify(constEvent&event)
2
{
3
....
4
}
der Aufrufer übergibt ein temporäres Objekt
1
intmain()
2
{
3
SystemmyMachine;
4
5
myMachine.notify(Event(Event::NextStateButton));
6
...
und gut ists. Kein Bedarf für new und einfacher ists auch noch.
Hier:
1
State*RedState::handleEvent(constEvent*event)
2
{
3
if(event->buttonsDown&Event::NextStateButton){
4
returnnewGreenState;
5
}
6
returnthis;
7
}
wieder ein new ohne guten Grund.
Mach die ganze States zb als Member in System rein. Der OffState kommt
in System rein (weil er in notify eine Sonderrolle hat), für den Rest
leutest du von System eine AmpelSystem ab, die als Member die für eine
Ampel möglichen Stati enthält. Jeder Status kriegt im Konstruktor mit,
zu welchem System er gehört und kann damit dann vom AmpelSystem einen
Pointer zu einem anderen Status anfordern. Wenn RedState einen
GreenState anfordert, dann erzeugt es keinen neuen GreenState, sondern
benutzt den One-and-Only GreenState, den es in AmpelSystem gibt.
(Ohne das jetzt kompiliert oder vollständig lauffähig gemacht zu haben)
In deinem ganzen System brauchst du keinen einzigen new. Dadurch
brauchst du auch keinen einzigen delete, wodurch sich hier
Ja, du hast schon Recht, die Speicherverwaltung in dem Ding ist schief
gelaufen. Einzig bei dem Event würde ich dir widersprechen -- ich sehe
kein Problem dabei, dass die Kontrolle für das Event an die Funktion
übergeben wird, die es bearbeitet (muss natürlich entsprechend
dokumentiert sein).
Die States jedes Mal neu zu erzeugen macht natürlich, wie du sagst, bei
diesem Beispiel auch keinen Sinn. Die Idee war aber, dass man in den
States später noch mehr Informationen speichert, und dann wird das
eventuell ein bisschen knifflig, wenn man von jedem State-Typ nur eine
Instanz hat. Im Endeffekt finde ich es also schon sinnvoller, für jeden
tatsächlich neuen Zustand vom System auch ein neues Objekt zu erzeugen.
Dazu siehe auch das Beispiel mit dem OffState, der sich halt einen
anderen Zustand merken kann. Das wird -- wenn das System insgesamt ein
bisschen komplizierter wird -- schwieriger, wenn es von jedem Zustand
nur eine Instanz gibt.
Aus demselben Grund habe ich auch die ganzen news verbaut: wenn so ein
State ein komplexes Objekt ist, mit vielleicht zwei Dutzend Membern,
dann will man das nicht jedes Mal kopieren, wenn man es zum Beispiel aus
einer Funktion zurückgibt. Für meine 8-Byte-Objekte ist das natürlich
völliger Unsinn, aber das sollte ja auch bloß ein Beispiel zum Erweitern
sein.
Also zusammenfassend: ja, ich war mit der Speicherverwaltung selbst
unzufrieden, nachdem ich es fertig geschrieben hatte, weil es dann doch
zu viele dynamische allocs geworden sind. Ganz ohne würde ich es aber
dann auch nicht machen.
Grüße,
Sven
Sven B. schrieb:> Ja, du hast schon Recht, die Speicherverwaltung in dem Ding ist schief> gelaufen. Einzig bei dem Event würde ich dir widersprechen -- ich sehe> kein Problem dabei, dass die Kontrolle für das Event an die Funktion> übergeben wird, die es bearbeitet
Wozu?
Weder muss sich die notify Funktion um die Lebensdauer eines Events
scheren, noch geht es die Funktion etwas an, ob ich diesen gleichen
Event vielleicht noch in eine 2.te Zustandsmaschine stecke, die ihn
ebenfalls auswerten will.
Sie muss es nicht wissen, also soll sie es auch nicht wissen.
Lass die Kontrolle der Dinge beeinander! Die Klasse die ein Objekt
anlegt, ist dafür zuständig dass es zerstört wird. Natürlich gibt es
Ausnahmen davon, aber hier liegt kein Grund dafür vor. Kein Mensch
braucht das hier an dieser Stelle.
> Aus demselben Grund habe ich auch die ganzen news verbaut
Du machst dir (und auch dem Computer) nur das Leben unnötig schwer.
> State ein komplexes Objekt ist, mit vielleicht zwei Dutzend Membern,> dann will man das nicht jedes Mal kopieren,
Wer redet denn von kopieren?
Ich hab nirgend gesagt, dass du das Objekt kopieren sollst.
Sven B. schrieb:> Die States jedes Mal neu zu erzeugen macht natürlich, wie du sagst, bei> diesem Beispiel auch keinen Sinn. Die Idee war aber, dass man in den> States später noch mehr Informationen speichert, und dann wird das> eventuell ein bisschen knifflig, wenn man von jedem State-Typ nur eine> Instanz hat. Im Endeffekt finde ich es also schon sinnvoller, für jeden> tatsächlich neuen Zustand vom System auch ein neues Objekt zu erzeugen.> Dazu siehe auch das Beispiel mit dem OffState, der sich halt einen> anderen Zustand merken kann. Das wird -- wenn das System insgesamt ein> bisschen komplizierter wird -- schwieriger, wenn es von jedem Zustand> nur eine Instanz gibt.
Überhaupt nicht.
Inheritance und Polymorphie arbeiten für dich.
> Die Idee war aber, dass man in den> States später noch mehr Informationen speichert, und dann wird das> eventuell ein bisschen knifflig, wenn man von jedem State-Typ nur eine> Instanz hat.
Entweder es ist der gleiche State-Typ oder er ist es nicht. Ist es
derselbe State-Typ, dann hat er auch die gleiche Logik. Verwässere nicht
dein Konzept!
Hi,
Karl Heinz Buchegger schrieb:> Sven B. schrieb:>> Ja, du hast schon Recht, die Speicherverwaltung in dem Ding ist schief>> gelaufen. Einzig bei dem Event würde ich dir widersprechen -- ich sehe>> kein Problem dabei, dass die Kontrolle für das Event an die Funktion>> übergeben wird, die es bearbeitet>> Wozu?>> Weder muss sich die notify Funktion um die Lebensdauer eines Events> scheren, noch geht es die Funktion etwas an, ob ich diesen gleichen> Event vielleicht noch in eine 2.te Zustandsmaschine stecke, die ihn> ebenfalls auswerten will.
Ja, wahrscheinlich hast du Recht.
>> State ein komplexes Objekt ist, mit vielleicht zwei Dutzend Membern,>> dann will man das nicht jedes Mal kopieren,> Wer redet denn von kopieren?> Ich hab nirgend gesagt, dass du das Objekt kopieren sollst.
Gut, wenn du das natürlich so machst, dass es von jedem Typ nur eins
gibt, umgehst du das natürlich. Das war aber nicht meine Absicht.
Aber irgendwie gefällt mir meine Version (mal von der Speicherverwaltung
abgesehen ;) besser:
Bei dieser Methode muss ich für jedes Event, das ich hinzufügen will,
einen neuen Member in State anlegen, und eine neue Member-Funktion.
Etwas unschön, aber gut, keine große Sache.
Was mich aber viel mehr stört, ist dass das System die States so
komplett enthält. Das Zustand vom System ist deshalb nämlich nicht durch
ein "State"-Objekt komplett beschrieben -- sondern du musst dazu alle
kennen. Wenn du also einen Zustand des Systems zum Beispiel speichern
willst, reicht es nicht, ein State-Objekt zu kopieren (bei meinem
Vorschlag könnte man sich sogar nur den Pointer merken, wenn die
Speicherverwaltung etwas weniger ungeschickt wäre), sondern man muss die
alle kopieren -- und zwar von Hand, weil's einzelne Attribute sind. Und
auch daran immer rumflicken, wenn man einen neuen State hinzufügt.
Vielleicht übersehe ich hier etwas, aber mir gefällt das nicht.
Grüße,
Sven
Sven B. schrieb:> Was mich aber viel mehr stört, ist dass das System die States so> komplett enthält.
Das stört mich überhaupt nicht.
Ganz im Gegenteil. Dadurch schaffe ich einen Box, in dem klar geregelt
ist, welche States zu welcher Maschine gehören. Niemand sagt ja, dass
ich im Programm nur eine einzige Zustandsmaschine habe. Da der RedState
zur Ampel gehört, kann er nur andere States die ebenfalls zur Ampel
gehören als seine Nachfolger benennen. Er kann nicht einen State aus der
Maschine für Bahnschranken benennen. Der gehört nicht zur Ampel und das
AmpelSystem Objekt hält keine States, die zum BahnschrankenSystem
gehören.
> Das Zustand vom System ist deshalb nämlich nicht durch> ein "State"-Objekt komplett beschrieben
Du sagst hier selber
der Zustand vom System
Da ist also vom System die Rede
> -- sondern du musst dazu alle> kennen.
Ja, das soll vorkommen. Wenn man eine Zustandsmaschine analysieren will,
muss man alle in dieser Maschine überhaupt möglichen Stati kennen.
> Wenn du also einen Zustand des Systems zum Beispiel speichern> willst, reicht es nicht, ein State-Objekt zu kopieren (bei meinem> Vorschlag könnte man sich sogar nur den Pointer merken, wenn die> Speicherverwaltung etwas weniger ungeschickt wäre), sondern man muss die> alle kopieren
Warum?
Das System Objekt, weiß ja, welches der jetzt gerade aktive Zustand ist.
Ausserdem sagst du ja jetzt gerade selber:
Du willst das System speichern. DAS SYSTEM, nicht einen einzelnen
Zustand! Das System, das ist aber die Gesamtheit aller Zustände. Woher
weißt du denn, ob sich nicht ein anderer Zustand ebenfalls speihern
will, weil er sich irgendwas wichtiges merken muss? Das kann nur der
Zustand wissen. Das System kennt alle Zustände die zu ihm gehören und
gibt jedem Gelegenheit, sich aufs File zu speichern.
> -- und zwar von Hand, weil's einzelne Attribute sind.
Na ja.
Die Phantasie hätt ich dir jetzt aber schon zugetraut, dass mans sich da
auch einen Vektor machen kann, denn man zum Speichern durchiteriert oder
was anderes erfindet.
> Und> auch daran immer rumflicken, wenn man einen neuen State hinzufügt.
Richtig.
Und das ist auch gut so!
Den der neue Zustand gehört zum Ampelsystem. Er gehört nicht zum
AndreaskreuzSystem oder zum BahnschrankenSystem.
So wie du es jetzt machst - da kannst du dir OOP auch sparen. Mach ein
paar Strukturen, mach ein paar Funktionen und gut ists. Hat auch den
Vorteil, dass du weniger Tipparbeit hast.
Aber wenn du OOP arbeiten willst, dann musst du das auch ernst nehmen.
Welche Objekte gibt es und noch viel wichtiger: Wie hängen sie zusammen
und wie kann ich diese Zusammenhänge im Code ausdrücken, so dass mir der
Compiler überwacht, dass diese Zusammenhänge gewährleistet sind.
> Welche Objekte gibt es und noch viel wichtiger: Wie hängen sie zusammen> und wie kann ich diese Zusammenhänge im Code ausdrücken, so dass mir der> Compiler überwacht, dass diese Zusammenhänge gewährleistet sind.
Die OOP Idee ermöglicht es dir 2 große, wichtige Zusammenhänge in
Klassenform auszudrücken
* IST EIN
Im Sinne von "Ein PKW ist ein Kraftfahrzeug"
class Kraftfahrzeug
{
};
class PKW : public Kraftfahrzeug
{
};
* HAT EIN (oder: besteht aus)
Im Sinn von "Ein Kraftfahrzeug hat einen Motor"
class Kraftfahrzeug
{
protected:
Motor motor_;
};
Wenn an dieser Stelle wieder verschiedene Motortypen möglich
sind, dann muss man leider aus technischen Gründen einen Pointer
benutzen. Das ändert aber nichts daran, dass eine HAT-EIN Beziehung
auf einen Klassenmember rausläuft.
Eine Zustandsmaschine besteht aus Zuständen (no na), also ....
Hi,
> Das stört mich überhaupt nicht.> Ganz im Gegenteil. Dadurch schaffe ich einen Box, in dem klar geregelt> ist, welche States zu welcher Maschine gehören.
Naja, das wäre mit Vererbung aber eleganter zu regeln. MySystemBaseState
: public BaseState, und alle States von MySystem müssen davon erben.
Dann ist auch klar, welche States zu welcher Maschine gehören.
> Niemand sagt ja, dass> ich im Programm nur eine einzige Zustandsmaschine habe. Da der RedState> zur Ampel gehört, kann er nur andere States die ebenfalls zur Ampel> gehören als seine Nachfolger benennen. Er kann nicht einen State aus der> Maschine für Bahnschranken benennen. Der gehört nicht zur Ampel und das> AmpelSystem Objekt hält keine States, die zum BahnschrankenSystem> gehören.
Wie gesagt, das lässt sich durch Vererbung sehr einfach erzwingen.
>> Das Zustand vom System ist deshalb nämlich nicht durch>> ein "State"-Objekt komplett beschrieben> Du sagst hier selber> der Zustand vom System> Da ist also vom System die Rede
Ja, aber wie das Wort "State" sagt, soll so ein State ein Zustand sein,
in dem sich das System befindet. Solch ein State soll das System
komplett beschreiben. Das war meine Idee. Natürlich kann man auch andere
Ideen haben, die funktionieren, aber so hatte ich mir das überlegt.
> Ja, das soll vorkommen. Wenn man eine Zustandsmaschine analysieren will,> muss man alle in dieser Maschine überhaupt möglichen Stati kennen.
Ich bin mir immer noch nicht sicher, welche Relevanz die Attribute von
StateA haben sollen, wenn das System sich zur Zeit in StateB befindet.
Was haben die für eine Bedeutung?
> Warum?> Das System Objekt, weiß ja, welches der jetzt gerade aktive Zustand ist.
Okay.
> Ausserdem sagst du ja jetzt gerade selber:> Du willst das System speichern. DAS SYSTEM, nicht einen einzelnen> Zustand!
Naja, das widerspricht jetzt deinem vorherigen Argument. Ich kann nur
mit einem davon argumentieren, also nehme ich mal dieses ;)
> Das System, das ist aber die Gesamtheit aller Zustände. Woher> weißt du denn, ob sich nicht ein anderer Zustand ebenfalls speihern> will, weil er sich irgendwas wichtiges merken muss? Das kann nur der> Zustand wissen. Das System kennt alle Zustände die zu ihm gehören und> gibt jedem Gelegenheit, sich aufs File zu speichern.
Wie gesagt, da verfolgst du einfach einen anderen Ansatz als ich. Mein
Ansatz ist: Das System ist durch eine Instanz von "State" komplett
beschrieben.
>> -- und zwar von Hand, weil's einzelne Attribute sind.> Na ja.> Die Phantasie hätt ich dir jetzt aber schon zugetraut, dass mans sich da> auch einen Vektor machen kann, denn man zum Speichern durchiteriert oder> was anderes erfindet.
Ja, aber richtig elegant wird das trotzdem nicht, weil man muss ja dann
den GreenState in dem Vektor identifizieren können... braucht man also
nochmal einen hässlichen enum oder sonstwas...
>> Und>> auch daran immer rumflicken, wenn man einen neuen State hinzufügt.> Richtig.> Und das ist auch gut so!> Den der neue Zustand gehört zum Ampelsystem. Er gehört nicht zum> AndreaskreuzSystem oder zum BahnschrankenSystem.
Hm, ja, schon, aber inwiefern macht das jetzt eine zusätzliche Stelle im
Code, die ich bei jeder Änderung anpassen muss, zu einem Vorteil?
Grüße,
Sven
Sven B. schrieb:> Naja, das wäre mit Vererbung aber eleganter zu regeln. MySystemBaseState> : public BaseState, und alle States von MySystem müssen davon erben.> Dann ist auch klar, welche States zu welcher Maschine gehören.
Zeig mal, wie du das mit der Methode der Basisklasse unterbringst :-)
Die Methode
bleibt unangetastet und wird auch nicht von einer abgeleiteten Klasse
überschrieben.
handleEvent liefert einen Pointer auf ein von State abgeleitete Klasse.
Irgendeine Klasse. Solange sie nur von State abgeleitet ist, passt das.
D.h. jede deiner State Klassen kann irgendeinen State Pointer liefern.
Selbst dann, wenn der gar nicht zu dieser Maschine gehört.
> Ja, aber wie das Wort "State" sagt, soll so ein State ein Zustand sein,> in dem sich das System befindet. Solch ein State soll das System> komplett beschreiben.
Moment.
Ein State ist also das komplette System?
Sorry - Sven
Ich versteh ja, dass du deine Systematik verteidigen willst, aber du
beginnst dich jetzt in Widersprüche zu verwickeln.
Entweder du hast ein System oder du hast States. Aber ein State kann
nicht das System sein, sonst würdest du eines von beiden nicht
benötigen.
> Hm, ja, schon, aber inwiefern macht das jetzt eine zusätzliche Stelle im> Code, die ich bei jeder Änderung anpassen muss, zu einem Vorteil?
Weil dich der COmpiler dazu zwingt, dass du nicht einfach irgendwas
machen kannst, sondern du einen neuen Zustand ins System regelrecht
einbauen musst. Und sowas verhindert Fehler. Alles was darauf basiert,
dass du dem Programmierer vertrauen musst, ist ein Garant dafür, dass er
irgendwann Fehler macht. Alles was du so gestaltest, dass der Compiler
es überprüfen kann, ist ein Garant dafür, dass derartige Fehler auch vom
Compiler gefunden werden.
Sven B. schrieb:>> <OOP-Lehrgang>> Ja -- mein System hat einen aktuellen Zustand. Einen. Nicht fünf.
Ich hab auch nur einen aktuellen Zustand.
Aber das Gesamtsystem kann 5 verschiedene Zustände annehmen. Einer davon
ist der aktuelle.
Sven B. schrieb:> Ich bin mir immer noch nicht sicher, welche Relevanz die Attribute von> StateA haben sollen, wenn das System sich zur Zeit in StateB befindet.> Was haben die für eine Bedeutung?
Das kommt jetzt aber sehr auf das System an.
In diesem Fall haben die States ja überhaupt keine Attribute.
Also was macht dir dann sorgen?
>> Ausserdem sagst du ja jetzt gerade selber:>> Du willst das System speichern. DAS SYSTEM, nicht einen einzelnen>> Zustand!> Naja, das widerspricht jetzt deinem vorherigen Argument.
Inwiefern soll das ein Widerspruch sein?
Ich sehe da ehrlich gesagt keinen Widerspruch.
> Ansatz ist: Das System ist durch eine Instanz von "State" *komplett*> beschrieben.
Wozu brauche ich dann 'das System' überhaupt noch?
Aus welchem Grunde hast du es eingeführt? Alles was in der System-Klasse
ist, könnte dann doch genausogut in der State-Klasse sein, wenn ein
State sowieso die komplette Maschine beschreibt?
Hi,
Karl Heinz Buchegger schrieb:> Sven B. schrieb:>>> Naja, das wäre mit Vererbung aber eleganter zu regeln. MySystemBaseState>> : public BaseState, und alle States von MySystem müssen davon erben.>> Dann ist auch klar, welche States zu welcher Maschine gehören.>> Zeig mal, wie du das mit der Methode der Basisklasse unterbringst :-)>> Die Methode>
> bleibt unangetastet und wird auch nicht von einer abgeleiteten Klasse> überschrieben.
Naja, handleEvent() muss dann natürlich einen MySystemState*
zurückgeben. Aber das ist ja kein Problem.
> handleEvent liefert einen Pointer auf ein von State abgeleitete Klasse.> Irgendeine Klasse. Solange sie nur von State abgeleitet ist, passt das.> D.h. jede deiner State Klassen kann irgendeinen State Pointer liefern.> Selbst dann, wenn der gar nicht zu dieser Maschine gehört.
Jaja, so wie das da steht natürlich schon. Aber ich wollte in diesem
Beispiel nicht anfangen eine Klasse State, und eine Klasse
MyMachineState : public State { } einzuführen, weil's einfach
überflüssig gewesen wäre. Aber es ist uns doch beiden klar, wie man es
machen würde.
>> Ja, aber wie das Wort "State" sagt, soll so ein State ein Zustand sein,>> in dem sich das System befindet. Solch ein State soll das System>> komplett beschreiben.> Moment.> Ein State ist also das komplette System?> Sorry - Sven> Ich versteh ja, dass du deine Systematik verteidigen willst, aber du> beginnst dich jetzt in Widersprüche zu verwickeln.
Nein, das ist genau das was ich meine: Ein State ist das komplette
System. Ist ja in meiner Implementierung auch so, die System-Klasse ist
nur ein Container für eine (!) Instanz von State mit ein paar
Funktionen, die diese manipulieren. Zusätzlich könnte die System-Instanz
noch ein paar statische Eigenschaften haben, die das System an sich
charakterisieren, sich aber nach dem Erstellen des Systems nicht mehr
ändern. Ich weiß schon, dass du was anderes willst ;)
> Entweder du hast ein System oder du hast States. Aber ein State kann> nicht das System sein, sonst würdest du eines von beiden nicht> benötigen.
Streng genommen könnte man in meinem Konzept die System-Klasse
weglassen, ja. Sieh es als Wrapper um eine Instanz von State, die immer
ausgetauscht wird.
>> Hm, ja, schon, aber inwiefern macht das jetzt eine zusätzliche Stelle im>> Code, die ich bei jeder Änderung anpassen muss, zu einem Vorteil?> Weil dich der COmpiler dazu zwingt, dass du nicht einfach irgendwas> machen kannst, sondern du einen neuen Zustand ins System regelrecht> einbauen musst. Und sowas verhindert Fehler. Alles was darauf basiert,> dass du dem Programmierer vertrauen musst, ist ein Garant dafür, dass er> irgendwann Fehler macht. Alles was du so gestaltest, dass der Compiler> es überprüfen kann, ist ein Garant dafür, dass derartige Fehler auch vom> Compiler gefunden werden.
Naja, das stimmt ja aber nicht. Wenn ich von den fünf Membern nur vier
in eine Datei schreibe, interessiert das den Compiler ja nicht...
Grüße,
Sven
Sven B. schrieb:> Naja, handleEvent() muss dann natürlich einen MySystemState*> zurückgeben.
Niemand zwingt dich dazu.
Für
currentState = currentState->handleEvent(event);
ist ein Returnwert als MySysteState* genausogut wie ein BaseState* oder
ein AmpelState* oder ein XYZState*
Solange die alle irgendwie von State abgeleitet sind, ist das für diese
Zuweisung absolut perfekt.
Sven B. schrieb:> Nein, das ist genau das was ich meine: Ein State ist das komplette> System. Ist ja in meiner Implementierung auch so, die System-Klasse ist> nur ein Container für eine (!) Instanz von State mit ein paar> Funktionen, die diese manipulieren.
(Für einen Pointer, aber ist egal)
class State
{
....
static State* currentState;
};
jetzt ist der currentState im State selber.
Ist also überhaupt kein Problem.
> Zusätzlich könnte die System-Instanz> noch ein paar statische Eigenschaften haben, die das System an sich> charakterisieren
Was jetzt.
Beschreibt jetzt ein State das komplette System oder nicht?
Edit: gerade gesehen, du sagst dann später ja selber, dass du das System
an sich gar nicht benötigst.
> Naja, das stimmt ja aber nicht. Wenn ich von den fünf Membern nur vier> in eine Datei schreibe, interessiert das den Compiler ja nicht...
Das ist richtig.
Aber da kommt dann ins Spiel, auf welche Art und Weise ich genau die in
einem System möglichen States beschreibe.
Wenn ich die als Member einzeln aufführe, dann muss ich drauf achten -
ja. Genauso wie ich in einem Copy Constructor (wenn ich ihn selber
schreibe) darauf achten muss, alle Member zu behandeln.
Erfinde ich mir einen Container in dem alle States drinnen sind, dann
brauch ich das nicht, hab aber den Mehraufwand, dass ich eine
Identifizierungsmöglichkeit (ein enum oder sonst was) benötige.
> Sieh es als Wrapper um eine Instanz von State, die immer
ausgetauscht wird.
Und die immer neu erzeugt wird!
Das war ja der Dreh und Angelpunkt der ganzen Sache.
Objekterzeugung ist vergleichsweise teuer!
Karl Heinz Buchegger schrieb:> (Für einen Pointer, aber ist egal)>> class State> {>> ....>> static State* currentState;> };>>> jetzt ist der currentState im State selber.> Ist also überhaupt kein Problem.
Tut mir Leid, ich verstehe nicht, was du hier sagen willst. Kannst du
das nochmal anders formulieren?
>> Zusätzlich könnte die System-Instanz>> noch ein paar statische Eigenschaften haben, die das System an sich>> charakterisieren>> Was jetzt.> Beschreibt jetzt ein State das komplette System oder nicht?> Edit: gerade gesehen, du sagst dann später ja selber, dass du das System> an sich gar nicht benötigst.
Ja, abgesehen von eventuellen festen Werten, die das System noch hat.
Aber wenn man die nicht mag, kann man die auch weglassen.
>> Naja, das stimmt ja aber nicht. Wenn ich von den fünf Membern nur vier>> in eine Datei schreibe, interessiert das den Compiler ja nicht...>> Das ist richtig.> Aber da kommt dann ins Spiel, auf welche Art und Weise ich genau die in> einem System möglichen States beschreibe.> Wenn ich die als Member einzeln aufführe, dann muss ich drauf achten -> ja. Genauso wie ich in einem Copy Constructor (wenn ich ihn selber> schreibe) darauf achten muss, alle Member zu behandeln.> Erfinde ich mir einen Container in dem alle States drinnen sind, dann> brauch ich das nicht, hab aber den Mehraufwand, dass ich eine> Identifizierungsmöglichkeit (ein enum oder sonst was) benötige.
Sicher, das Problem ist lösbar. Aber ein Vorteil dieser Methode ist es
nicht gerade.
Vor allem ist mir immer noch nicht klar, was du dir davon versprichst,
mehrere Instanzen von State für ein System zu haben. Was bringt dir das
an Gewinn gegenüber meiner "Ein System ist eigentlich nur eine Instanz
von State"-Variante?
Grüße,
Sven
Karl Heinz Buchegger schrieb:> Und die immer neu erzeugt wird!> Das war ja der Dreh und Angelpunkt der ganzen Sache.> Objekterzeugung ist vergleichsweise teuer!
Naja. Von was reden wir hier denn. Ich dachte, es geht um ein paar
Buttons, die gedrückt werden, und eine 2GHz-CPU, die die States
wechselt. Da brauchen wir über das alloc von den 16 Bytes ja wohl nicht
zu streiten.
Wenn das auf einem 8-Bit uC laufen soll, sieht die Sache natürlich
anders aus, und dann gebe ich auch sofort zu, dass meine Lösung
ungeeignet ist.
Sven B. schrieb:> Vor allem ist mir immer noch nicht klar, was du dir davon versprichst,> mehrere Instanzen von State für ein System zu haben. Was bringt dir das> an Gewinn gegenüber meiner "Ein System ist eigentlich nur eine Instanz> von State"-Variante?
Das mir der Compiler bei
State* RedState::handleEvent(const Event* event)
{
if ( event->buttonsDown & Event::NextStateButton ) {
return new SchrankenZuState;
}
return this;
}
auf die Finger klopft, weil "Schranke Zu" bei einer Ampel nichts
verloren hat! Der gehört nicht zur Ampel, aber nichts und niemand
hindert mich daran das zu tun!
Sven B. schrieb:> Karl Heinz Buchegger schrieb:>> Und die immer neu erzeugt wird!>> Das war ja der Dreh und Angelpunkt der ganzen Sache.>> Objekterzeugung ist vergleichsweise teuer!> Naja. Von was reden wir hier denn. Ich dachte, es geht um ein paar> Buttons, die gedrückt werden, und eine 2GHz-CPU, die die States> wechselt. Da brauchen wir über das alloc von den 16 Bytes ja wohl nicht> zu streiten.
Ok. Ich geb mich geschlagen.
Karl Heinz Buchegger schrieb:> Sven B. schrieb:>> Vor allem ist mir immer noch nicht klar, was du dir davon versprichst,>> mehrere Instanzen von State für ein System zu haben. Was bringt dir das>> an Gewinn gegenüber meiner "Ein System ist eigentlich nur eine Instanz>> von State"-Variante?>> Das mir der Compiler bei>> State* RedState::handleEvent(const Event* event)> {> if ( event->buttonsDown & Event::NextStateButton ) {> return new SchrankenZuState;> }> return this;> }>>> auf die Finger klopft, weil "Schranke Zu" bei einer Ampel nichts> verloren hat! Der gehört nicht zur Ampel, aber nichts und niemand> hindert mich daran das zu tun!Das soll der komplette Vorteil sein? Und dafür speicherst du fünfmal
so viele Objekte wie ich und beschwerst dich dann über die Performance
meiner Lösung? Das finde ich etwas merkwürdig.
Es hindert dich in keinem Fall jemand daran, einfach
reinterpret_cast<State*>(0xDEADBEEF) zurückzugeben. Ein assert in
"System" würde solcherart Fehler also sogar zuverlässiger vermeiden.
Aber gut, dann mache ich das halt anders, wenn diese eine Zeile pro
State Machine zuviel Schreibaufwand ist:
Sven B. schrieb:> Das soll der komplette Vorteil sein?
Schön langsam frag ich mich wozu du überhaupt Klassen hast.
Ein Pointer, 5 Funktionen und gut ists.
Wer braucht denn schon Sicherheit oder Organisation oder
wiederverwendbare Module in seinen Programmen?
1
typedefvoid*(*stateFnct)(Event*event);
2
3
stateFnctcurrentState;
4
5
stateFnctredState(Event*event)
6
{
7
..
8
returngrnState;
9
}
10
11
statFnctgrnState(Event*event)
12
{
13
...
14
returnyellowState;
15
}
16
17
stateFnctyellowState(Event*event)
18
{
19
...
20
returnredState;
21
}
22
23
intmain()
24
{
25
Eventev;
26
27
currentState=redState;
28
29
currentState=(StateFnct)currentState(&ev)
30
currentState=(StateFnct)currentState(&ev)
31
currentState=(StateFnct)currentState(&ev)
32
}
und gut ists.
Alles andere ist doch nur Tipparbeit für nichts.
Dieser Beitrag war jetzt etwas unsachlich, weil er in keinerlei
Zusammenhang zu den vorherigen Beiträgen steht ;)
Das Beispiel was ich gepostet habe zeigt doch, dass man dasselbe type
checking auch mit meinem Aufbau bekommen kann. Wo ist also das Problem?
Gruß,
Sven
Sven B. schrieb:> Das Beispiel was ich gepostet habe zeigt doch, dass man dasselbe type> checking auch mit meinem Aufbau bekommen kann. Wo ist also das Problem?
Was hab ich davon, wenn ich bei jeder neuen Zustandsmaschine erst recht
wieder das Rad von vorne aufrollen und mehr oder weniger bei 0 anfangen
muss?
Da kann ich mir das ganze Geplänkel auch gleich sparen und spar mir 40%
Tipparbeit.
J. W. schrieb:> if (NOT TASTE2 AND NOT TASTE3) {> Ampel1( ROT );> state = S_WARTE_GELB;> }> else if (TASTE3)> {> state = S_BLINK_EINS;> }> else state = S_AUS;
Ein wirklich sehr schönes Beispiel, wie man Sachen unnötig kompliziert
machen kann, wenn man sich standhaft weigert, eine vernünftige
Entprellroutine zu verwenden.
Bravo!
Karl Heinz Buchegger schrieb:> Was hab ich davon, wenn ich bei jeder neuen Zustandsmaschine erst recht> wieder das Rad von vorne aufrollen und mehr oder weniger bei 0 anfangen> muss?
Ist bei Beitrag "Re: Zustandsmaschine: Tabelle wohl nicht sinnvoll, oder mit Pointern auf Funktionen?" nicht der
Fall.
Falk Brunner schrieb:> Mann O Mann. Wie man Probleme mit HighTec löst, die man ohne sie nie> hätte.> Kein Wunder dass heute jede Eieruhr einen 200 MHZ ARM9 braucht . . .
Für eine Eieruhr ist das zugegebenermaßen Quatsch.
Davon abgesehen, wenn man das geschickt aufschreibt, wird der GCC daraus
Code erzeugen, der nicht wesentlich komplizierter ist als der mit dem
Switch.
Ingo schrieb:> Man beachte die Uhrzeiten! Schlaft ihr garnicht mal n bissel?
Ja, es ist beunruhigend ;)
Grüße,
Sven