Imagine I have a class that represents a simple washing machine. It can perform following operations in the following order: turn on -> wash -> centrifuge -> turn off. I see two basic alternatives:
I can have a class WashingMachine with methods turnOn(), wash(int minutes), centrifuge(int revs), turnOff(). The problem with this is that the interface says nothing about the correct order of operations. I can at best throw InvalidOprationException if the client tries to centrifuge before machine was turned on. I can also use a separete Program class that will pass centrifuge revs and wash minutes to the WashingMachine and will simplify these methods.
I can let the class itself take care of correct transitions and have the single method nextOperation(). The problem with this on the other hand, is that the semantics is poor. Client will not know what will happen when he calls the nextOperation(). Imagine you implement the centrifuge button’s click event so it calls nextOperation(). User presses the centrifuge button after machine was turned on and ups! machine starts to wash. I will probably need a few properties on my class to parameterize operations, or maybe a separate Program class with washLength and centrifugeRevs fields, but that is not really the problem.
Which alternative is better? Or maybe there are some other, better alternatives that I missed to describe?
I'd have a 'high level' State Machine
class which controls the entry/running/exit of each state (where states could be things like 'filling', 'washing', 'rinse', 'emptying', 'spin dry', etc)
Draw up a State Transition Diagram of all the states you need, including (for each state)
You may or may not need the entry/exit conditions (e.g. you can force the conditions with an entry/exit action in some cases). For safety reasons though, some conditions can be good (e.g. exiting from a 'standby' state or entry into a 'dangerous' state like spin dry)
You also create Transitions
, which define the links between states. A transition has
Again, you may not need all of these, and many transitions will have only the 'from' and 'to' states specified.
In both cases, try to keep each State
and Transition
as simple as it needs to be (try to put the conditions/actions where they make the most sense, obviously there's potential double-up of these to be defined in a State
and a Transition
because of the generic design)
It should be pretty obvious at this point that you can make a fairly generic State
class which includes abstract/overloadable functions for all of these things, and likewise for the Transition
class. The State Machine
class can call each of these member functions as required, based on the transitions requested of it.
If you make it especially generic, then the States
and Transitions
can be registered with the State Machine
at construction time, or you might just code the State Machine
to contain them all.
You can then request transitions, either from within states (i.e. a particular state can know when it has ended, and know which state to go to next) or you could have an external class which controls the state transitions (each state is then very simple, purely taking care of its own actions, and the external class decides the order and timing of transitions).
In my experience with these things, it's a good idea to separate the high level logic of deliberate order/timing of each action and the low level logic of reaction to some hardware event (e.g. transition out of the 'filling' state when the water level is reached).
It's a really generic design though, and you can achieve exactly the same functionality a bunch of different ways -- there's rarely a single right way of doing things...