views:

503

answers:

10

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:

A)

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.

B)

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?

A: 

The first is exactly what I'd do. wash and centrifuge would check the flag that tracks if the machine is on and throw an exception if it's not, but otherwise there is no mandatory order of operations; you could call centrifuge before wash if you really felt the need

Michael Mrozek
A: 

Erm, a little weird but here goes;

I'd be looking at even delegates. So for a particular wash cycle type, heave, light, suddsy, economic etc I'd have say an interface for each.

Then, with the use of generics, I'd create the appropriate wash type I want.

The wash type would then have a wash event which delegates, in order, the cycles to perform.

griegs
+5  A: 

I think that turnOn(), wash(), centerfuge() etc. should be private/protected methods. The public interface should be doTheWash(WashMode mode). The washing machine itself knows the modes it supports and how to make them work, the user of the washing machine need not get involved with the order or duration of operations. It is reasonable to expect the author of the washing machine class to call the private methods in a sensible order.

ScottS
A: 

The washing machine should know how to properly run a load. Perhaps before washing a load, certain configuration items are expected to be set (with defaults, if unset), like what type of load (lights vs darks) or size (small, medium, large). The user (another class) should know how to properly configure a load to run, but for washing a load, the washing machine should only need to expose one method, something like washLoad. Internally it would manage what needs to be called when to wash a load of laundry.

Alternatively, you could still expose public functions for turning the washer on and off, then if washLoad were called when the washer was off, it would be appropriate to throw an exception. Additionally, in this scenario, I would probably make the settings for the washer set via a single method setLoadOptions that would take another class the would be something like LoadOptions and defined whatever characteristics you wanted configurable. I would have this method also throw an exception if the washer was not on.

Timothy Carter
+11  A: 

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)

  1. what requirements there are before you enter the state (entry conditions)
  2. what needs to happen when you enter the state (entry actions)
  3. what happens during the state (the task itself)
  4. what requirements there are before you can leave the state (exit conditions)
  5. what happens when you exit the state (exit actions)

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

  1. a 'from' state
  2. a 'to' state
  3. transition conditions (can the transition happen?
  4. transition actions (what needs to happen when you transition)

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...

drfrogsplat
+1: always draw the state transition diagram, always.
Mark E
I think this is a great answer! I would add that I find it useful to think of the UI running within a thread and the Washing machine states/transitions/work running in another. Also, one could use your States as nodes, and Transitions as edges (rather than having Transition compose 'from' and 'to' state) in a graph structure. This would allow things like enumerating 'to' states for a given node. I like using JUNG for graph structures.
harschware
yep, nodes/edges is basically the same idea, like i said the design is pretty generic, so whether Transitions have 'from' and 'to' states, or whether States have a list of available Transitions isn't too important, there's a lot of ways these can be implemented
drfrogsplat
A: 

On my washing machine you have access to two knobs. I'm a little confused as to what the limitations are in your case.

  1. Load size (Small, Medium, Large)
  2. Cycle knob (Perm Press, Knits & Delicates, Regular)
    • Each cycle contains different "Phases" at which it can be activated at

When you "Activate" a cycle (by turning the knob), the machine will begin running if you turn it to the middle or beginning of a cycle.

Here is a really, really basic example of how I would translate this into a class:

class WashingMachine
{
    public void Activate(Cycle c, LoadSize s);
    ...
}
Joe Philllips
A: 

Lately I have been going functional and using immutable objects with Martin Fowler/JQuery chaining fluent style.

  • I use the type system as much as possible as it makes refactoring.
  • With my method the compiler will force you to do it right.
  • Because the objects are somewhat immutable its easier to prove that mathematically this state machine is deterministic.

code:

public class Washer {
    public void exampleRun() {
        new On()
            .wash(30)
            .spin(100)
            .turnOff();
    }
    public abstract static class State {}
    public static class On extends State {
        public Wash wash(int minutes) {
            return new Wash(minutes);
        }
    }
    public static class Wash extends State {
        private int minutes;
        public Wash(int minutes) {
            super();
            this.minutes = minutes;
        }

        public Spin spin(int revs) {
            return new Spin(revs);
        }

    }
    public static class Spin extends State {
        private int revs;
        public Spin(int revs) {
            super();
            this.revs = revs;
        }
        public Off turnOff() {
            return new Off();
        }
    }
    public static class Off extends State{}
}
Adam Gent
While that's nice, it doesn't really model the real behavior of a washing machine. The user (on most models) can't typically choose the order and length of states, though they can usually choose between several cycle templates. Although, I suppose your ExampleRun could be a cycle template that is chosen.
Mystere Man
@Mystere Man The point is the states are immutable objects and only the states know which is the next state. I'm pretty sure for a Washer you could get this to work.
Adam Gent
@Adam Gent - nothing against pattern and code, it really looks good! And the OP asked for a solution for a simple state machine. Just for the real world - modern machines vary the program, for example based on measuring the quality of the used water - like: if it's to dirty, wash again. That would introduce conditions and branches.
Andreas_D
+1  A: 

Most western washing machines use a timer to move from cycle to cycle. This timer can be thought of as a state machine of sorts. However, it's important to realize that a wasching machine runs itself, not the user. The user sets the initial mode, and then it goes on about its business.

So internally, you may have Wash, Rinse, Spin private functions, the actual interface would be SetCycle() and Start() and Stop(). You may also have some additional properties, like Water Level, Agitation Speed, Water Temperature, etc...

Start causes the time to advance, which after a period of time enters the next state, until finally it is complete.

Mystere Man
A: 

Option A is like a manual washer while option B is like an automatic washer. Both options are useful to their targeting users:

A user of option A can enjoy manually programming the whole process (like doubling the washing circle, or teaching the little one what if you turn off the machine while it's in centrifuging circle).

A user of option B can enjoy the simplicity and write a dummy serials - teach your self washing in 21 seconds:-)

Codism
+1  A: 

Every washing machine has a controller and a program. For older and simple machines, program and controller are integrated into a rather complicated knob, modern and more expensive machines have a computer.

In either case, the machine has additional subsystems and the controller notifies the subsystems to perform some action, like lock the door, heat water until 40°C or spin the drum.

The Controller itself knows the sequence, which is pretty linear and timer based for old machines or complex for modern systems which vary the sequence based on sensor data. But in either case it executes a series of commands which may notify sensors and actors.

If you look at the washing system from this perspective, you may want to make use of the Command pattern inside the controller to model the washing program and of the Observer pattern for notifying the subsystems (Like: The door switch listens to the controller for a signal to lock the door).

Andreas_D