Combining State and Singleton Patterns to Create a State-Machine
Simple State Design Pattern Implementation
In a typical implementation of the design pattern, State is either an interface or abstract class, with each state of the state machine being Singleton classes. My example is going to be slightly different. I will implement the state machine using a state interface and Java enums to implement the Singleton. Using an enum is the recommended way to implement a Singleton in Java.First, let's come up with a simple state interface
State.java
public interface State
{
void goNext(Context input);
void goPrevious(Context input);
}
FirstState.java
public enum FirstState implements State
{
INSTANCE;
private FirstState () { }
@Override
public void goNext(Context context)
{
context.setState(SecondState.INSTANCE);
}
@Override
public void goPrevious(Context context)
{
// DO NOTHING (No previous state to go to)
}
}
SecondState.java
public enum SecondState implements State
{
INSTANCE;
private SecondState () { }
@Override
public void goNext(Context context)
{
context.setState(ThirdState.INSTANCE);
}
@Override
public void goPrevious(Context context)
{
input.setState(FirstState.INSTANCE);
}
}
ThirdState.java
public enum ThirdState implements State
{
INSTANCE;
private ThirdState () { }
@Override
public void goNext(Context context)
{
// DO NOTING (This is the last state)
}
@Override
public void goPrevious(Context context)
{
input.setState(SecondState.INSTANCE);
}
}
Now, we need to implement the context class.
Context.java
public final class Context
{
private State current;
public Context ()
{
current = FirstState.INSTANCE;
}
public void next()
{
current.goNext(this);
}
public void previous()
{
current.goPrevious(this);
}
public void setState(State current)
{
this.current = current;
}
public State getCurrentState()
{
return current;
}
}
In essence, I have created a state-machine with three states.
What's happening so far?
As far as the context object is concerned, there is only a single State that changes states when the correct stimulus (trigger) is applied. In the state-machine diagram above, you can see the "trigger" for one state to go to the next state is labeled "next." This label is exactly the name of one of the methods in the Context class. This is because the context object is the state-machine's controller. When the context receives an instruction to go to the next state (i.e. from a navigation button), it instructs the current State to go to the next state. The context doesn't know what that next state is. Only the State instance knows what that next State is based on what the current state is. In the provided example, the third state doesn't have a valid "next" state. Therefore, when the context tells this state to change to the next, it doesn't do anything and the context keeps ThirdState as the current state. The other two states will change to the next state and will update the context with the next state.Enhancement to the State Interface
It might be nice if we had a way to ask a state if it has a previous or next state. After all, a state-machine with a single state is pretty much worthless. You could say that a State most likely has a previous state and a next state. For example, the ThirdState enum knows it doesn't have a valid "next" state. The problem is that adding such methods will require a change in the State interface, which in turn will break every state enum created. Well, I have good news and bad news. The bad news is that this change will require a change in the interface. The good news is that it will not break the current state enum implementations. At least, not entirely.Java 8 introduced the concept of Default Methods. Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces. I want to enhance the State interface by adding a method to find out if the state has a current state and another to find out if the state has a next state. In addition to these two methods, I will add a default method to give a State object the ability to return its identity by returning it's class name. This method will come very handy when integrating this state-machine with a Java Swing interface.
State.java
public interface State
{
...
default boolean hasNext() { return true; }
default boolean hasPrevious() { return true; }
default String getName()
{
return this.getClass().getSimpleName();
}
}
You specify that a method definition in an interface is a default method with the default keyword at the beginning of the method signature. With this interface, you do not have to modify any of the State enums. However, both these default methods return true and this not the case with the first and third states. While the first state has a next state, it doesn't have a previous. The opposite is true of the third state. So, what do we do now? All we need to do is to override the appropriate method in whichever class needs it. For example, the FirstState needs to override the hasPrevious() method, and the ThirdState needs to override the hasNext() method. The SecondState can remain unmodified.
FirstState.java
public enum FirstState implements State
{
...
@Override
public boolean hasPrevious() { return false; }
}
ThirdState.java
public enum ThirdState implements State
{
...
@Override
public boolean hasNext() { return false; }
}
Read next: Simple Wizard controlled by this state-machine using Swing for the GUI.
Comments
Post a Comment