State Design Pattern in Java

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

If you have a few years of experience in the Java ecosystem, and you're interested in sharing that experience with the community (and getting paid for your work of course), have a look at the "Write for Us" page.
Cheers. Eugen

1. Overview

In this tutorial, we’ll introduce one of the behavioral GoF design patterns – the State pattern.

At first, we’ll give an overview of its purpose and explain the problem it tries to solve. Then, we’ll have a look at the State’s UML diagram and implementation of the practical example.

2. State Design Pattern

The main idea of State pattern is to allow the object for changing its behavior without changing its class. Also, by implementing it, the code should remain cleaner without many if/else statements.

Imagine we have a package which is sent to a post office, the package itself can be ordered, then delivered to a post office and finally received by a client. Now, depending on the actual state, we want to print its delivery status.

The simplest approach would be to add some boolean flags and apply simple if/else statements within each of our methods in the class. That won’t complicate it much in a simple scenario. However, it might complicate and pollute our code when we’ll get more states to process which will result in even more if/else statements.

Besides, all logic for each of the states would be spread across all methods. Now, this is where the State pattern might be considered to use. Thanks to the State design pattern, we can encapsulate the logic in dedicated classes, apply the Single Responsibility Principle and Open/Closed Principle, have cleaner and more maintainable code.

3. UML Diagram

In the UML diagram, we see that Context class has an associated State which is going to change during program execution.

Our context is going to delegate the behavior to the state implementation. In other words, all incoming requests will be handled by the concrete implementation of the state.

We see that logic is separated and adding new states is simple – it comes down to adding another State implementation if needed.

4. Implementation

Let’s design our application. As already mentioned, the package can be ordered, delivered and received, therefore we’re going to have three states and the context class.

As we can see, it contains a reference for managing the state, notice previousState(), nextState() and printStatus() methods where we delegate the job to the state object. The states will be linked to each other and every state will set another one based on this reference passed to both methods.

The client will interact with the Package class, yet he won’t have to deal with setting the states, all the client has to do is go to the next or previous state.

Next, we’re going to have the PackageState which has three methods with the following signatures:

Here, we point to the next state which will occur after the package is ordered. The ordered state is our root state and we mark it explicitly. We can see in both methods how the transition between states is handled.

Package ordered, not delivered to the office yet.
Package delivered to post office, not received yet.
Package was received by client.
This package is already received by a client.
Package was received by client.

As we’ve been changing the state of our context, the behavior was changing but the class remains the same. As well as the API we make use of.

Also, the transition between the states has occurred, our class changed its state and consequentially its behavior.

6. Downsides

State pattern drawback is the payoff when implementing transition between the states. That makes the state hardcoded, which is a bad practice in general.

But, depending on our needs and requirements, that might or might not be an issue.

7. State vs. Strategy Pattern

Both design patterns are very similar, but their UML diagram is the same, with the idea behind them slightly different.

First, the strategy pattern defines a family of interchangeable algorithms. Generally, they achieve the same goal, but with a different implementation, for example, sorting or rendering algorithms.

In state pattern, the behavior might change completely, based on actual state.

Next, in strategy, the client has to be aware of the possible strategies to use and change them explicitly. Whereas in state pattern, each state is linked to another and create the flow as in Finite State Machine.

8. Conclusion

The state design pattern is great when we want to avoid primitive if/else statements. Instead, we extract the logic to separate classes and let our context object delegate the behavior to the methods implemented in the state class. Besides, we can leverage the transitions between the states, where one state can alter the state of the context.

In general, this design pattern is great for relatively simple applications, but for a more advanced approach, we can have a look at Spring’s State Machine tutorial.