Combining State and Singleton Patterns to Create a State-Machine

In my previous two posts, I discussed real-world applications for the Singleton and State design patterns. In this article, I am going to illustrate how to combine both of these patterns to create a simple wizard.

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);
}
Now that we have an interface defined, we can derive as many states as we need. For this example, three states should be sufficient to demonstrate the wizard's functionality. Java allows enum to extend interfaces. Therefore, we can use them just like a regular class.

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);
  }
}
As previously stated, because we want our concrete states to be Singletons, we use enum instead of a regular class to ensure any given state is only instantiated once. In other words, we only need a single instance of the first state, a single instance of the second state, and a single instance of the third state. No matter how many times we transition to a state, if an instance of that state already exists, it will use it.

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

Popular posts from this blog

Implementing Interfaces with Java Records

Customizing Java Records

Exception Handling: File CRUD Operations Example