Object-oriented – Finite state machine menu design

finite-state machineobject-orientedobject-oriented-design

I'm creating an FSM in python (it's a step sequencer and sample pad based on a Raspberry Pi 2).

Right now there are two states and the third is the Menu. This is handled by a class System which handles all the states. The Menu state has to edit the attributes of the other states, so I passed the other states to its constructor.

    class State(object):
        def buttonPress(self, numberButton):
            raise NotImplementedError

    class StepSequencer(State):
        def buttonPress(self, numberButton):
            ...

    class SamplePad(State):
        def buttonPress(self, numberButton):
            ...

    class Menu(State):
        def __init__(self,stepSequencer,samplePad):
            self.stepsequencer = stepSequencer
            self.samplepad = samplePad
        def buttonPress(self, numberButton):
            ...
        def setMenuItem(self, currentMenuItem):
            self.currentMenuItem = currentMenuItem

    class MenuItem(object):
        def __init__(self, text):
            self.text = text

    class System(object):
        def __init__(self):
            self.stepsequencer = StepSequencer()
            self.samplepad = SamplePad()
            self.menu = Menu(self.stepsequencer, self.samplepad)
        def setState(self,state):
            self.state = state
        def buttonPress(self, numberButton):
            self.state.buttonPress(numberButton)

I can't figure out how to create the structure for the menu. I thought of creating a class MenuItem for every menu item so the Menu state has all these objects and can change the current MenuItem, however, there are some things that I cannot overcome:

  • how can I declare all the menu items and pass them to the Menu state to create dynamically the structure of the menu?

  • having this menu:

    Step Sequencer settings:
        Set samples
        Set volume
    Sample Pad setting:
        Set samples
        Set volume
    

    for example, if I want to have a slider that sets the volume, do I have to create another state to handle this? This state can be another state of the System or it must be a substate of the Menu state?

  • can I execute the code of each state using this logic? :

    while(system.state = system.stepsequencer):
        ...
    while(system.state = system.menu):
        ...
    

    The state is changed by a listener in another thread. This seems a very unorthodox way of handling states but it seems to work. If this is effective, how can I handle the Menu substates?

Best Answer

Probably the best way to implement FSMs in OOP languages is sticking to the State Design Pattern

That pattern provides a general structure to design and implement FSMs, dividing the FSM in two "sides": The context and the state, being the state a hierarchy of all the different states the context can have (a context can only have a very limited amount of states at a single time, usually only one). Then, the state transitions are modeled, usually either in the client (the class that uses the context will decide when to explicitly transition to a new state, making the context instance behave differently), or in the states (when each state is responsible of checking the transition conditions and perform the actual transition). The first approach has the advantage of a lower cohesion between states, making them reusable in implementations of other FSMs, but with a much more complex client class. The second approach has the opposite side effects.

In your case, I'd model System with the role of Context, State with the role of State, and Menu (and its submenus) as different ConcreteStates. If StepSequencer and SamplePad are to be exclusive of a single menu/submenu, they'd be attributes of that individual state. Otherwise, (i.e., shared or persistent between menus), as attributes of System. That decision will depend on your actual needs.

Now, about your design (and this is a little off-topic), StepSequencer and SamplePad don't look like actual states (neither by their uses nor by their names). Personally, I'd bet it's that way just because their interface is apparently identical to that of states (def buttonPress(self, numberButton):). If that's the reason, and you did it that way to avoid duplicating code, don't. It's okay to have code duplicated in certain occasions (e.g., when the base interface is likely to change in different directions over time, as stated by Robert C. Martin in his book Clean Architecture). Keep this in mind and consider deriving them from a different base class other than State.