In behavioral design patterns, the State pattern and the Strategy pattern are close brothers; they are very similar. Let's first look at their common class diagram and compare them side by side, as shown in the figure:

They look very similar, don't they? Just looking at this UML, we can't tell the difference. Next, let's combine examples to compare the differences between the two. The example below is from "Head First Design Patterns".


Strategy Pattern

The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. This pattern lets the algorithm vary independently from clients that use it.

A company developed a duck game that features various kinds of ducks: mallard ducks, redhead ducks, rubber ducks... How can we implement these ducks in a program? Seeing this scenario, it's easy to think of first defining a superclass Duck for all ducks and encapsulating the common methods and properties in it. For example, all ducks can swim, and all ducks have their own appearance:

Example

public abstract class Duck {
    public void swim(){
        System.out.println("All duck can swim!");
    }
    public abstract void display();
    public void fly(){
        System.out.println("Fly~~~");
    }
    public void quack(){
        System.out.println("Quack quack quack~");
    }
}

But soon, we find a problem with this superclass definition. Not all ducks can fly, and not all ducks quack (assume rubber ducks squeak). As long as they inherit this superclass, all ducks will fly, and all ducks will quack.

What to do?

Solution 1

The first solution that comes to mind is method overriding in subclasses. Simple: just override the fly method in subclasses that can't fly.

But the drawback is obvious: wouldn't all non-flying subclasses need to override it? Suppose 50 kinds of ducks can't fly; how huge would the rewriting and maintenance workload be?

Solution 2

Okay, so we think of a second method: continue designing sub-abstract classes, such as the abstract subclass FlyNoQuackDuck that can fly but not quack, the abstract subclass QuackNoFlyDuck that can quack but not fly, the abstract subclass NoQuackNoFlyDuck that can neither quack nor fly, the abstract subclass FlyAndQuackDuck that can both fly and quack...

As we write, we find this method doesn't work either; it's too inflexible, and the more parts that change, the more such abstract subclasses we need to define.

Solution 3

Then what method is good? We reflect that the above problem arises because we habitually try to use inheritance to implement the changing parts. In fact, we can extract the changing parts and use composition to implement them.

Three design principles are used here:

  • Identify the aspects of your application that may vary and separate them.
  • Program to an interface, not an implementation.
  • Favor composition over inheritance.

Applying the first design principle, we separate the changing methods fly() and quack() and encapsulate them into two behavior interfaces. Then, based on different needs, we design different behavior classes that implement the interfaces.

Applying the second and third design principles, we compose the two member-variable interfaces into the Duck class and assign them dynamically in subclasses.

The overall "class diagram" is as follows:

Summary

This solution perfectly solves our problem. Using the idea of the "Strategy Pattern", we extract the changing parts and compose them into the class. According to different subclasses, we can "set" different behavior subclasses to dynamically change behavior.

Code Implementation

Two behavior interface classes

Example

public interface FlyBehavior {
    public void fly();
}

public interface QuackBehavior {
    public void quack();
}

Different behavior classes implementing the flight interface

Example

public class FlyNoWay implements FlyBehavior{
    public void fly(){
        System.out.println("I can't fly...");
    }
}

public class FlyWithWings implements FlyBehavior{
    public void fly(){
        System.out.println("Fly~~~");
    }
}

public class FlyWithRocket implements FlyBehavior{

    public void fly(){
        System.out.println("With a rocket launcher, fly~~~");
    }
}

Different behavior classes implementing quacking

Example

public class Quack implements QuackBehavior{
    public void quack(){
        System.out.println("Quack quack quack~");
    }
}


public class Squeak implements QuackBehavior{
    public void quack(){
        System.out.println("Squeak squeak squeak~");
    }
}


public class MuteQuack implements QuackBehavior{
    public void quack(){
        System.out.println("I can't quack...");
    }
}

The superclass composed with implementing interfaces

Example

public abstract class Duck {
    protected FlyBehavior flyBehavior;
    protected QuackBehavior quackBehavior;

    public void swim(){
        System.out.println("All duck can swim!");
    }

    public abstract void display();

    /**
* Dynamically change flight behavior
     */

    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    /**
* Dynamically change quacking behavior
     */

    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }

    public void performFly(){
        flyBehavior.fly();
    }

    public void performQuack(){
        quackBehavior.quack();
    }
}

Different duck classes

Example

/**
* Mallard Duck
 */

public class MallarDuck extends Duck{

    public MallarDuck() {
        //Can fly
        flyBehavior = new FlyWithWings();
        //Quacks
        quackBehavior = new Quack();
    }

    @Override
    public void display() {
        System.out.println("Looks like a mallard");
    }

}

/**
* Redhead Duck
 */

public class RedHeadDuck extends Duck{

    public RedHeadDuck() {
        //Can fly
        flyBehavior = new FlyWithWings();
        //Quacks
        quackBehavior = new Quack();
    }

    @Override
    public void display() {
        System.out.println("Looks like a redhead duck");
    }

}

/**
* Rubber Duck
 */

public class RubberDuck extends Duck{

    public RubberDuck() {
        //Can't fly
        flyBehavior = new FlyNoWay();
        //Squeaks
        quackBehavior = new Squeak();
    }

    @Override
    public void display() {
        System.out.println("Looks like a rubber duck");
    }

}

State Pattern

The State Pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class.

The State Pattern is very similar to the Strategy Pattern. It also encapsulates the "state" of the class and automatically transitions when performing actions, so that the same action of the class in different states shows different results. The difference from the Strategy Pattern is that this transition is "automatic" and "unconscious".

The class diagram of the State Pattern is as follows

The class diagram of the State Pattern is exactly the same as that of the Strategy Pattern; the difference lies in their intent. The Strategy Pattern controls which strategy the object uses, while the State Pattern automatically changes the state. You should be clear after reading the case below.

Now you have a requirement for a candy machine, and it needs to be implemented in Java.

Let's analyze it. The functionality of the candy machine can be divided into four actions and four states as shown in the figure below:

In different states, the same action has different results. For example, in the "inserted 25 cents" state, turning the crank will dispense candy; but in the "no 25 cents" state, turning the crank will prompt you to insert a coin first.

After a brief thought, we write the following candy machine implementation code

Example

public class NoPatternGumballMachine{
    /*
* Four states
     */

    /**No coin state*/
    private final static int NO_QUARTER = 0;
    /**Coin inserted state*/
    private final static int HAS_QUARTER = 1;
    /**Sold state*/
    private final static int SOLD = 2;
    /**Sold out state*/
    private final static int SOLD_OUT = 3;

    private int state = SOLD_OUT;
    private int candyCount = 0;

    public NoPatternGumballMachine(int count) {
        this.candyCount = count;
        if(candyCount > 0)
            state = NO_QUARTER;
    }

    /*
* Four actions
     */


    /**
* Insert coin
     */

    public void insertQuarter() {
        if(NO_QUARTER == state){
            System.out.println("Insert coin");
            state = HAS_QUARTER;
        }
        else if(HAS_QUARTER == state){
            System.out.println("Please do not insert a coin repeatedly!");
            returnQuarter();
        }
        else if(SOLD == state){
            System.out.println("Coin inserted, please wait for candy");
            returnQuarter();
        }else if(SOLD_OUT == state){
            System.out.println("Candy is sold out");
            returnQuarter();
        }
    }

    /**
* Return coin
     */

    public void ejectQuarter() {
        if(NO_QUARTER == state){
            System.out.println("No coin, cannot eject");
        }
        else if(HAS_QUARTER == state){
            returnQuarter();
            state = NO_QUARTER;
        }
        else if(SOLD == state){
            System.out.println("Cannot return coin, dispensing candy, please wait");
        }else if(SOLD_OUT == state){
            System.out.println("No coin inserted, cannot return coin");
        }
    }

    /**
* Turn crank to dispense candy
     */

    public void turnCrank() {
        if(NO_QUARTER == state){
            System.out.println("Please insert a coin first");
        }
        else if(HAS_QUARTER == state){
            System.out.println("Turn crank, ready to dispense candy");
            state = SOLD;
        }
        else if(SOLD == state){
            System.out.println("Already turned the crank, please wait");
        }else if(SOLD_OUT == state){
            System.out.println("Candy is sold out");
        }
    }

    /**
* Dispense candy
     */

    public void dispense() {
        if(NO_QUARTER == state){
            System.out.println("No coin inserted, cannot dispense candy");
        }
        else if(HAS_QUARTER == state){
            System.out.println("this method don't support");
        }
        else if(SOLD == state){
            if(candyCount > 0){
                System.out.println("Dispensing a candy");
                candyCount --;
                state = NO_QUARTER;
            }
            else{
                System.out.println("Sorry, candy is sold out");
                state = SOLD_OUT;
            }
        }else if(SOLD_OUT == state){
            System.out.println("Sorry, candy is sold out");
        }
    }

    /**
* Refund coin
     */

    protected void returnQuarter() {
        System.out.println("Returning coin...");
    }

}

As can be seen from the code, the candy machine presents different results for corresponding actions based on its current state. This code can already meet our basic requirements, but with a little thought, you will feel that this implementation code seems too complicated, has poor extensibility, and lacks an object-oriented style.

Suppose a new requirement forces us to add a state; then we need to modify every action method and add another else statement each time. And if the requirements change and an action in a certain state needs to be modified, we also have to change four methods at the same time. Such work would be tedious and headache-inducing.

What to do? One of the six design principles

Identify the aspects of your application that may vary and separate them.

In the candy machine, the state is the part that keeps changing; different states have different actions. We can definitely extract it.

The new design idea is as follows:

First, we define a State interface. In this interface, each action of the candy machine has a corresponding method.

Then implement a state class for each state of the machine. These classes will be responsible for the machine's behavior in the corresponding state.

Finally, we need to get rid of the old conditional code and replace it by delegating actions to the state classes.

Define a State interface

Example

public abstract class State {
    /**
* Insert coin
     */

    public abstract void insertQuarter();

    /**
* Return coin
     */

    public abstract void ejectQuarter();

    /**
* Turn crank to dispense candy
     */

    public abstract void turnCrank();

    /**
* Dispense candy
     */

    public abstract void dispense();

    /**
* Refund coin
     */

    protected void returnQuarter() {
        System.out.println("Returning coin...");
    }
}

Implement state classes for each state of the machine

Example

/**
* No coin state
 */

public class NoQuarterState extends State{
    GumballMachine gumballMachine;

    public NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("You inserted a coin");
        //Transition to coin inserted state
        gumballMachine.setState(gumballMachine.hasQuarterState);
    }

    @Override
    public void ejectQuarter() {
        System.out.println("No coin, cannot eject");
    }

    @Override
    public void turnCrank() {
        System.out.println("Please insert a coin first");
    }

    @Override
    public void dispense() {
        System.out.println("No coin inserted, cannot dispense candy");
    }

}

/**
* Coin inserted state
 */

public class HasQuarterState extends State{
    GumballMachine gumballMachine;

    public HasQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("Please do not insert a coin repeatedly!");
        returnQuarter();
    }

    @Override
    public void ejectQuarter() {
        returnQuarter();
        gumballMachine.setState(gumballMachine.noQuarterState);
    }

    @Override
    public void turnCrank() {
        System.out.println("Turn crank, ready to dispense candy");
        gumballMachine.setState(gumballMachine.soldState);
    }

    @Override
    public void dispense() {
        System.out.println("this method don't support");
    }

}

/**
* Sold state
 */

public class SoldState extends State{
    GumballMachine gumballMachine;

    public SoldState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("Coin inserted, please wait for candy");
        returnQuarter();
    }

    @Override
    public void ejectQuarter() {
        System.out.println("Cannot return coin, dispensing candy, please wait");
    }

    @Override
    public void turnCrank() {
        System.out.println("Crank already turned, please wait");
    }

    @Override
    public void dispense() {
        int candyCount = gumballMachine.getCandyCount();
        if(candyCount > 0){
            System.out.println("Dispensing one candy");
            candyCount--;
            gumballMachine.setCandyCount(candyCount);
            if(candyCount > 0){
                gumballMachine.setState(gumballMachine.noQuarterState);
                return;
            }
        }

        System.out.println("Sorry, candy is sold out");
        gumballMachine.setState(gumballMachine.soldOutState);
    }

}

/**
* Sold-out state
 */

public class SoldOutState extends State{
    GumballMachine gumballMachine;

    public SoldOutState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("Candy is sold out");
        returnQuarter();
    }

    @Override
    public void ejectQuarter() {
        System.out.println("No coin inserted, cannot return coin");
    }

    @Override
    public void turnCrank() {
        System.out.println("Candy is sold out");
    }

    @Override
    public void dispense() {
        System.out.println("Candy is sold out");
    }

}

Delegate the candy machine's actions to the state class

Example

public class GumballMachine extends State{
    public State noQuarterState = new NoQuarterState(this);
    public State hasQuarterState = new HasQuarterState(this);
    public State soldState = new SoldState(this);
    public State soldOutState = new SoldOutState(this);

    private State state = soldOutState;
    private int candyCount = 0;

    public GumballMachine(int count) {
        this.candyCount = count;
        if(count > 0)
            setState(noQuarterState);
    }

    @Override
    public void insertQuarter() {
        state.insertQuarter();
    }
    @Override
    public void ejectQuarter() {
        state.ejectQuarter();
    }
    @Override
    public void turnCrank() {
        state.turnCrank();
    }
    @Override
    public void dispense() {
        state.dispense();
    }

    public void setState(State state) {
        this.state = state;
    }

    public State getState() {
        return state;
    }

    public void setCandyCount(int candyCount) {
        this.candyCount = candyCount;
    }

    public int getCandyCount() {
        return candyCount;
    }

}

It can be seen that with this design, the candy machine doesn't need to know about state changes at all; it only needs to call the state's methods. State changes happen inside the state. This is the "State Pattern".

If another state is added at this point, the candy machine doesn't need to make any changes; we only need to add another state class, and then add the transition process in the related state class methods.

Don't understand? There's also an article about the State Pattern:https://dzone.com/articles/state-pattern-simplified

Original link: https://blog.csdn.net/z55887/article/details/73198039

Original link: https://blog.csdn.net/z55887/article/details/60608898