When a class has a handful of finite states and needs a way to handle actions differently according to each state, the first approach might be to create a cascade of if ... else if ... else statements. This can be tedious as it requires a lot of thinking to juggle each state, not to mention the new room for errors to be introduced! To help manage this problem of managing object state, the state pattern can be utilized!

Assumptions

What is the State Pattern?

The state pattern allows an object's behavior to change based on the internal state of the object. This is done in an attempt to simulate the object's class changing without it actually needing to do so.

<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/StatePattern_01.png" alt="state pattern UML diagram" style="max-width:95%;"> </div>

Key components:

  • Context: a class that acts as a wrapper for a state object; a change in state object allows the class to remain the same but behavior to vary.
  • State: an interface defining the actions that can be taken at any point in time.
  • Concrete State: Implements the state interface. This object is used by the context to simulate a change in behavior.

> Using this pattern, developers can create object's that are similar to state machines.

Conceptualization

When developing a software application, you might choose to follow a <a href="https://www.devmaking.com/learn/sdlc/" target="_blank" style="color:inherit;">software development life cycle</a> methodology to help the project run smoothly. If we were to model a basic waterfall approach in code, it would progress one stage at a time from the planning phase to the deployment phase.

These phases could easily be modeled as states of the project using the state pattern!

<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/StatePattern_02.png" alt="project development UML diagram" style="max-width:95%;"> </div>

To begin modeling the waterfall approach, let's create our state interface, ProjectStage. The interface should have a method identical to one in our Project class which will be our context, with a single variation of it accepting a context to change the state of:

// State interface:
interface ProjectStage {
   void makeProgress(Project context);
 }
// States:
class PlanningStage implements ProjectStage {
   void makeProgress(Project context) {
     print("Finished the planning stage of the project");
     context.setState(new DesignStage());
   }
 }

class DesignStage implements ProjectStage {
   void makeProgress(Project context) {
     print("Finished the design stage of the project");
     context.setState(new ImplementationStage());
   }
 }

class ImplementationStage implements ProjectStage {
   void makeProgress(Project context) {
     print("Finished the implementation stage of the project");
     context.setState(new TestingStage());
   }
 }

class TestingStage implements ProjectStage {
   void makeProgress(Project context) {
     print("Finished the testing stage of the project");
     context.setState(new DeploymentStage());
   }
 }

class DeploymentStage implements ProjectStage {
   void makeProgress(Project context) {
     print("The project has been deployed!");
     print("There's no more work to be done!");
   }
 }

Now all of the states our context can be in will implement this interface! Let's put together our context class, Project:

// Context:
class Project {
   // Current state:
   ProjectStage currentStage;
   
   Project() {
     // Set a default state:
     print("Starting a new project");
     currentStage = new PlanningStage();
   }
   
   void setState(ProjectStage stage) {
     this.currentStage = stage;
   }
   
   ProjectStage getState() {
     return currentStage;
   }
   
   // Notice!
   void makeProgress() {
     currentStage.makeProgress(this);
   }
 }

If you noticed, our context class has the same makeProgress method as our state interface, and it just calls to the current state's method of the same name with itself passed as the context.

If you were to implement more than one method in your state interface, you would want the names of the methods to be identical to the methods in the context class; this allows the context to simply "forward" the method call to the state object.

Client Code:

static void main(String[] args) {
   // Make our context:
   Project myApp = new Project();
   // Change the context state to progress through the project:
   myApp.makeProgress();
   myApp.makeProgress();
   myApp.makeProgress();
   myApp.makeProgress();
   myApp.makeProgress();
 }

> Challenge: model an iterative project using the state pattern!

Output:

Starting a new project
Finished the planning stage of the project
Finished the designing stage of the project
Finished the implementation stage of the project
Finished the testing stage of the project
The project has been deployed!
There's no more work to be done!