State Machine objektorientierter machen


  • Gesperrt

    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 und std::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;
    }
    

  • Gesperrt

    @Schlangenmensch sagte in State Machine objektorientierter machen:

    @meuterei Warum die typedef bei den enums?

    Vorgabe vom Prof. 😞


  • Gesperrt

    @Schlangenmensch

    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.


  • Gesperrt

    @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 Map state_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


  • Gesperrt

    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 Impementierung actionsStartStopRunning[Event::bla](); oder actionsStartStopRunning[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 einfach int ist und man dann doch den Bezeichner aus einem anderen enum nehmen kann.


Anmelden zum Antworten