State Machine objektorientierter machen
-
Wahrscheinlich ergibt das daraus ableitbare Diagramm nicht viel Sinn ... aber wie kann man das switch-case vermeiden, und wie kann man die Funktionsarrays (Zeile 17 bis 56) vermeiden, ohne dass dabei Redundanzen entstünden?
Und haben alle Deklarationen (Enums, Arrays, Variablen, Funktionen) das richtige Namensschema?
#include <iostream> typedef enum { STOPPED, STARTED, WORKING, } State; typedef enum { start_stop, reset, work, } Event; void (*actionsStartStopRunning[])(void) = { [](void) { std::cout << "Started" << std::endl; }, [](void) { std::cout << "Stopped" << std::endl; }, [](void) { std::cout << "running..." << std::endl; }, }; void (*actionsDoNothing[])(void) = { [](void) { std::cout << "already stopped" << std::endl; }, [](void) { std::cout << "start first" << std::endl; }, [](void) { std::cout << "already running..." << std::endl; }, }; void (*actionsTearDown[])(void) = { [](void) { std::cout << "graceful stop" << std::endl; }, [](void) { std::cout << "immediate stop" << std::endl; }, }; State currentState = STOPPED; void state_based_state_machine_next(Event event) { switch (currentState) { case STOPPED: if (event == start_stop) { actionsStartStopRunning[0](); currentState = STARTED; } if (event == reset) { actionsDoNothing[0](); } if (event == work) { actionsDoNothing[1](); } break; case STARTED: if (event == start_stop) { actionsStartStopRunning[1](); currentState = STOPPED; } if (event == reset) { actionsStartStopRunning[1](); currentState = STOPPED; } if (event == work) { actionsStartStopRunning[2](); currentState = WORKING; } break; case WORKING: if (event == start_stop) { actionsTearDown[0](); currentState = STOPPED; } if (event == reset) { actionsTearDown[1](); currentState = STOPPED; } if (event == work) { actionsDoNothing[2](); } break; } } int main(int argc, char const *argv[]) { // Example: state_based_state_machine_next(start_stop); state_based_state_machine_next(reset); state_based_state_machine_next(work); state_based_state_machine_next(start_stop); state_based_state_machine_next(work); state_based_state_machine_next(start_stop); state_based_state_machine_next(reset); return 0; }
-
@meuterei Warum die typedef bei den enums? Warum die voids bei den Lamdas?
Ich würde mit
enum class
undstd::map
arbeiten. Macht zwar nicht das selbe, wie dein Code, aber als Beispiel:enum class Event { start, reset, work, }; std::map<Event, std::function<void(void)>> actionsStartStopRunning { { Event::start, []() { std::cout << "Started" << std::endl; } }, { Event::reset, [](void) { std::cout << "Stopped" << std::endl; } }, { Event::work, [](void) { std::cout << "running..." << std::endl; }, } }; int main(int argc, char const* argv[]) { // Example: actionsStartStopRunning[Event::start](); return 0; }
-
@Schlangenmensch sagte in State Machine objektorientierter machen:
@meuterei Warum die typedef bei den enums?
Vorgabe vom Prof.
-
Das ist jetzt aber nicht mehr state-based
-
@meuterei Es ist eine alternative zu deinen Funktionspointer Arrays und kein vollständiger Zustandsautomat.
Wenn es dir, wie im Titel beschrieben, um einen objektorientierten Zustandsautomaten geht kannst du dir vlt das State Pattern anschauen.
-
@Schlangenmensch sagte in State Machine objektorientierter machen:
std::map<Event, std::function<void(void)>> actionsStartStopRunning
{
{
Event::start,
{
std::cout << "Started" << std::endl;
}
},Blöde Frage, aber du verknüpfst damit doch ein Event an eine Action. Eine Action ist aber sowohl vom aktuellen State wie auch vom aktuellen Event abhängig (siehe Eingangsposting). Da fehlt noch was, imho...
-
@meuterei sagte in State Machine objektorientierter machen:
Da fehlt noch was, imho...
Ja und? Mir ging es darum eine sprechendere Alternative zu den Funktionspointer Arrays zu zeigen. Außerdem kann man mit so was auch das
switch
ersetzen. Mir ging es nie darum, da einen Zustandsübergang zu implementieren oder einen Teil deines Automaten. Von mir aus, nenn die Mapstate_a_actions
, dann fehlt aber der Übergang in den neuen Zustand.Wenn du das objektorientiert haben möchest, fang doch an eine objektorientierte Lösung zu designen. Was brauchst du um einen Zustandsautomat darzustellen? Was löst Änderungen aus? Welchen Einfluss haben Änderungen?
Also, ganz klassisch objektorientiert: Was für Klassen hast du, was können die Klassen und wie stehen die miteinander in Verbindung
-
Danke.
@meuterei sagte in State Machine objektorientierter machen:
std::map<Event, std::function<void(void)>> actions
Wäre
std::map<std::string, std::function<void(void)>> actionsMap
denn nicht sinnvoller?Das Klassendiagramm wäre einfach ... Man braucht States, Events, Actions und eine Programmablauflogik (Zustandsübergangsfunktionen) ...
-
@meuterei Kommt halt immer drauf an, was man unter einfacher versteht. Das an eine
enum class
zu hängen, macht das ganze typsicher.
Zum Beispiel würde bei meiner ImpementierungactionsStartStopRunning[Event::bla]();
oderactionsStartStopRunning[State::stopped]()
schon zur Compiletime schief gehen.Wogegen bei einem String, gerade wenn man den direkt übergibt, man schnell irgendwo einen Tippfehler haben kann. Dann fliegt einem der Fehler erst zur Runtime um die Ohren.
Oder, wenn du zum Beispiel zwei Maps mit Funktionen und für jede eine extra
enum class
hast, kannst du nicht mehr ausversehen über Kreuz durcheinander kommen und ausversehen das Enum bzw. den Bezeichner aus einer anderen Map nehmen.Daher halte ich die Lösung mit
enum class
für weniger fehleranfälig und damit für einfacher.Aber Achtung, normale
enums
haben den Vorteil nicht, da der Unterliegende Typ einfachint
ist und man dann doch den Bezeichner aus einem anderenenum
nehmen kann.