 |
Implementing the State Design Pattern using Enums
Posted by ryano on January 31, 2005 at 04:14 PM | Comments (10)
Recently I've been reading "Head First Design Patterns" as well as
"Java 1.5 Tiger - A Developer's Notebook". Both are really good
books by the way. I was reading the chapter on the State design
pattern when it occurred to me that the new Enum feature in J2SE 5.0
would be a perfect way to implement the state design pattern.
Typically, the state pattern is used to model a state transition graph allowing an object to modify its
behavior as the state of the graph changes. The pattern
typically defines a "state" interface containing a method declaration for
each state transition in the graph. Next, a class is created
for each state in the state graph. These state classes implement
the "state" interface. Clients hold a reference to the current
state which it treats as a "state" interface
type. As the client invokes methods on the current state,
the concrete state object performs some operation and updates the
current state in the client. This decouples the client from the state management details.
A simple example of a state machine is a light switch. The switch
has two states, on and off. The switch also has two transitions,
turnOn and turnOff. If the switch is in the off state and
receives a command to turnOn the switch transitions to the on
state. If the switch is in the off state and is told to turn off,
the switch does nothing since it is already off. If the switch is
in the on state and is told to turnOff the switch transitions to the
off state. Finally, if the switch is in the on state and is told
to
turn on the switch does nothing.
Following the typical state pattern implementation, the "state"
interface would contain two methods, turnOn and turnOff. Two
classes would be created to represent the two states, on and off.
Each class would implement the "state" interface. The client
would hold a reference to an object of the "state" interface type and call the
appropriate methods on this object as events occur.
Using the Enum construct in J2SE 5.0, the entire state pattern can be
contained in an enumerated type definition. The abstract methods
declared in the enumerated type take the place of the "state" interface
and each enumeral implements the abstract methods much like the
concrete state classes implement the "state" interface.
The following enumerated type encapsulates the switch states and
transitions:
enum State { ON(1) { public State turnOn() { System.err.println("Already ON"); return ON; } public State turnOff() { System.err.println("Turning OFF"); return OFF; } }, OFF(0) { public State turnOn() { System.err.println("Turning ON"); return ON; } public State turnOff() { System.err.println("Already OFF"); return OFF; } };
private int val; State(int val) { this.val = val; }
public abstract State turnOn(); public abstract State turnOff(); }
Here is a simple Driver that exercises the Enumerated type:
public class Driver { private State state = State.OFF; public void go() { System.out.print("turnOn()\t"); state = state.turnOn(); System.out.print("turnOn()\t"); state = state.turnOn(); System.out.print("turnOff()\t"); state = state.turnOff(); System.out.print("turnOff()\t"); state = state.turnOff(); System.out.print("turnOn()\t"); state = state.turnOn(); System.out.print("turnOn()\t"); state = state.turnOn();
} public static void main(String[] args) { new Driver().go(); } }
Each enumeral represents a state and each abstract method represents a
state transition. Each enumeral implements the state
transitions
and acts accordingly. As new states and transitions are
introduced the
user can simply update this enumerated type. This seems like an
interesting way to implement the state design pattern. It also localizes the changes to one source file (excluding the Client). In the original version of the pattern the "state" interface would have to be updated with new transition methods. Each state class that implements the interface would need to add implementations of the new methods and for each new state a new class would be created.
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Although interesting as a use of enum in Tiger, this is really just an implementation of the whole state transition diagram in one class. Essentially, each enumeral implements a "row" in the transition diagram, where the events specify columns.
So, of your main argument for this approach is localizing changes - it's back to procedural programming and a giant switch statement (altough broken nicely in functions). When a new transition method is added, it still has to be added to all enumerals (as the transitions are defined abstract). When a new state is added, you still have to add a new enumeral that implements all the transitions.
So, basically, the amount of the code is the same, you just put it in one big file. Smells like C spirit...
Posted by: kirillcool on February 01, 2005 at 07:13 AM
-
We all love the big advantage that OOP design gives to the programmer, when it comes to modularisation of a project. But as everywhere in live things can get done too much.
From my point of view realizing a controlling state machine in just one class to use it as the center knot that references to the specialized classes on a fairly high level of the class tree is a very elegant solution. May the amount of code not be less, you achieve a better overview to a certain logic functionality that may be to complex to read split over different classes.
Especially enhancing functionality through enheritance, is the very spirit of OOP.
I think, I am going to use a kind of this structure in my running project. Thanks for the tip.
Posted by: wound on February 01, 2005 at 10:53 AM
-
This is the stupidest idea I've ever seen
interface DriverState
{
public void switch(Driver driver);
public String status();
}
public class OnState implements DriverState
{
public void switch(Driver driver)
{
driver.turnOff();
}
public String status()
{
return "On";
}
}
public class OffState implements DriverState
{
public void switch(Driver driver)
{
driver.turnOn();
}
public String status()
{
return "Off";
}
}
public class Driver
{
private DriverState state;
public void turnOn()
{
state = new OnState();
}
public void turnOff()
{
state = new OffState();
}
public Driver()
{
state = new OffState();
}
public void go() {
state.switch(this);
System.out.println(state.status());
state.switch(this);
System.out.println(state.status());
state.switch(this);
System.out.println(state.status());
state.switch(this);
System.out.println(state.status());
state.switch(this);
System.out.println(state.status());
state.switch(this);
System.out.println(state.status());
state.switch(this);
System.out.println(state.status());
}
public static void main(String[] args) {
new Driver().go();
}
}
Posted by: malby on February 01, 2005 at 06:13 PM
-
note: view source to get the formatted version.
Posted by: malby on February 01, 2005 at 06:13 PM
-
Ideally Driver would actually implement a Driver interface, because the DriveState interface is coupled to the Driver implementation, which I don't like.. You could have
interface Driver
{
void turnOn();
void turnOff();
}
interface DriverState
{
public void switch(Driver driver);
public String status();
}
rather than directly coupling to a concrete class.
Ofcourse, given the fact you actually want to use enums as a way to represent state, you probably wouldn't care for loosely coupled code.
Posted by: malby on February 01, 2005 at 06:17 PM
-
Malby,
While the code you supplied seems to work nicely for the simple example
of a light switch, it omits some of the functionality of the eumerated
type version.� What if you were required to generate error
messages when a user invoked a state transition that was illegal for
the current state or states had multiple valid transitions.� For
example, in my cheesy code you get an error when
telling a switch that is already on to turnOn. In your code you would
have to add a
transition method to your DriverState interface and implement the
method in each state class causing modifications to three source
files.� If you added a new state with new transition methods you
would have to modify the interface, all existing state classes and
create any new state classes.
As for loose coupling, this was meant to be a quick and dirty solution
but you could always move the abstract methods from the enumerated type to an interface and have the enumerated type implement this new interface.
I also like the idea of letting the enumerated type handle the state
transitions, returning the next state to the driver.� Your code
has a dependency between your state classes and the
driver since each state class calls into the driver to set the next
state.� Although, you did repost fixing the issue by proposing
the driver implement an interface.� The enumerated type code knows
nothing about the driver (client).
All in all looks like 6 of one, half dozen of another, and to be
truthful the enumerated type solution is all contained in a single
source file which I think is a nice bonus.� Opinions will vary.
Posted by: ryano on February 03, 2005 at 06:43 AM
-
Why would you have to modify all the existing State classes? Each state is only interested in one method on the driver interface.
Adding a new method to the interface does not require you to modify the all the existing state classes. Oh wait, it does in your implementation.
The negatives you point out in the code I provided all apply to the example you gave, if not even more so. Here you have state implementing methods that are of no interest to the object handling the state.
"Your code has a dependency between your state classes and the driver since each state class calls into the driver to set the next state.�"
I'm curious how many times have you developed a generic state class which has no direct coupling to the object it is representing state for?
Add a third state to your example, when a switch is turned on, it is first in a warming state before it actually turns on.
Posted by: malby on February 03, 2005 at 08:37 PM
-
Malby,
OK so in the case of adding new states and transitions you have to add the transition methods to your Driver interface and then implement these method in the Driver class. Then you need to add any new state classes. I'm still thinking 6 of one, half dozen of another and the enum still has the benefit of being contained in a single source file. I'll put you down as no vote for implementing the state design pattern using an enum.
Posted by: ryano on February 04, 2005 at 06:25 AM
-
I was reading "Java 1.5 Tiger - ADN" today and had the same thought about the 'State Pattern' and enums. Hit number 1 after Googling "java tiger enum 'state pattern'" brought me here.
kirillcool has a good point. But for cases when your code is reasonably short, replacing the whole pattern implementation (N plus one or two classes) with one (short) class, to me is elegant and concise coding.
Posted by: gmcauley on April 20, 2005 at 04:09 PM
-
The goal of having a single source file for implementing the State pattern can also be achieved by using inner classes. Also, expect changes in your code that have you add states, not methods. Events that must be handled should be objects themselves (either adhering to some interface or extending some base class or both). This keeps interface changes to a minimum.
Using enums, your initialisation code gets cleaner and less error-prone because an added state is immediately known as state (opposed to having to instantiate an instance of your inner state class).
Another advantage is that it allows other objects doing switches on the state, which makes life easier for the users of your software as well.
Posted by: victork on December 08, 2005 at 01:22 AM
|