Head First 状态模式

0 阅读6分钟

一、定义

状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

这个模式将状态封装成独立的类,并将动作委托到代表当前状态的对象。我们知道行为会随着状态而改变。
一个对象“看起来好像修改了它的类”是什么意思呢?从客户的视角来看:如果说你使用的对象能够完全改变它的行为,那么你会觉得,这个对象实际上是从别的类实例化而来的。然而,实际上,你知道我们是在使用组合通过简单引用不同的状态对象来造成类改变的假象。

现在,让我们来看看状态模式的类图:

state.png

状态模式和策略模式的类图是一样的,但是这两个模式的差别在于它们的意图。

对状态模式而言,我们将一群行为封装在状态对象中,Context的行为随时可以委托给那些状态对象中的一个。随着时间的流逝,当前状态在状态对象集合中游走改变,以反映出Context的内部状态,因此,Context的行为会随着改变,但是Context的客户对于状态对象了解不多,甚至是浑然不觉。

而对策略模式而言,客户通常主动指定Context所要组合的策略对象是哪一个。现在,固然策略模式让我们具有弹性,能够在运行时改变策略,但对于某个Context对象来说,通常只有一个最适当的策略对象。比方说,有些鸭子(比如绿头鸭)被设置成利用典型的飞行行为进行飞翔,而有些鸭子(比如橡皮鸭和诱饵鸭)使用的飞行行为只能让它们紧贴地面。

一般来说,我们把策略模式想成是除了继承之外的一种弹性替代方案。如果你使用继承定义了一个类的行为,你将被这个行为困住,甚至要修改它都很难。有了策略模式,你可以通过组合不同的对象来改变行为。

我们状态模式想成是不用在Context中放置许多条件判断的替代方案。通过将行为包装进状态对象中,你可以通过在Context内简单的改变状态对象来改变Context的行为。

本文以糖果机为例,下面是糖果机的状态图

状态图.png

下表是状态机的状态行为表格:

image.png

下面,让我们来看看代码实现:

/**
 * 这是一个公共的状态接口,定义了GumballMachine所有随状态变化而发生的行为
 */
public abstract class State {

    abstract void insertQuarter();

    abstract void ejectQuarter();

    abstract boolean turnCrank();

    abstract void dispense();
}

/**
 * 没有25分钱的状态
 */
public class NoQuarterState extends State {

    GumballMachine gumballMachine;

    /**
     * 通过构造器得到GumballMachine的引用,然后将它记录在实例变量中
     * @param gumballMachine
     */
    public NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    /**
     * 如果有人投入25分钱,则改变机器的状态到HasQuarterState
     */
    @Override
    public void insertQuarter() {
        gumballMachine.setState(gumballMachine.getHasQuarterState());
    }

    @Override
    public void ejectQuarter() {
        System.out.println("you haven't insert a quarter");
    }

    @Override
    public boolean turnCrank() {
        System.out.println("you turned, but there's no quarter");
        return false;
    }

    @Override
    public void dispense() {
        System.out.println("you need to pay first");
    }
}
/**
 * 有25分钱的状态
 */
public class HasQuarterState extends State {

    Random randomWinner = new Random(System.currentTimeMillis());
    
    GumballMachine gumballMachine;

    /**
     * 通过构造器得到GumballMachine的引用,然后将它记录在实例变量中
     * @param gumballMachine
     */
    public HasQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("you can't insert another quarter");
    }

    /**
     * 如果有人要求退钱,我们就把钱退回,并改变机器的状态到NoQuarterState
     */
    @Override
    public void ejectQuarter() {
        gumballMachine.setState(gumballMachine.getNoQuarterState());
    }

    /**
     * 如果有人转动曲柄,并且中奖了,就改变机器的状态到WinnerState。
     * 如果有人转动曲柄,但是没有中奖,就改变机器的状态到SoldState。
     * @return
     */
    @Override
    public boolean turnCrank() {
        System.out.println("you turned the crank");
        int winner = randomWinner.nextInt(10);
        if (winner == 0 && gumballMachine.getCount() > 0) {
            gumballMachine.setState(gumballMachine.getWinnerState());
        } else {
            gumballMachine.setState(gumballMachine.getSoldState());
        }
        return true;
    }

    @Override
    public void dispense() {
        System.out.println("no gumball dispense");
    }
}
package org.dengxl.designpattern.state;

/**
 * 发放糖果状态
 */
public class SoldState extends State {

    GumballMachine gumballMachine;

    /**
     * 通过构造器得到GumballMachine的引用,然后将它记录在实例变量中
     * @param gumballMachine
     */
    public SoldState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("please wait, we're already giving you a gumball");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("sorry, you already turned");
    }

    @Override
    public boolean turnCrank() {
        System.out.println("turn the crank twice");
        return true;
    }

    /**
     * 发放糖果。
     * 如果糖果没有了,则改变机器的状态到SoldOutState。
     * 如果还有剩余糖果,则改变机器的状态到NoQuarterState。
     */
    @Override
    public void dispense() {
        gumballMachine.releaseBall();
        if (gumballMachine.getCount() == 0) {
            gumballMachine.setState(gumballMachine.getSoldOutState());
        } else {
            gumballMachine.setState(gumballMachine.getNoQuarterState());
        }
    }
}

package org.dengxl.designpattern.state;

public class WinnerState extends State {

    GumballMachine gumballMachine;

    /**
     * 通过构造器得到GumballMachine的引用,然后将它记录在实例变量中
     * @param gumballMachine
     */
    public WinnerState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("already inserted quarter");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("can't eject quarter");
    }

    @Override
    public boolean turnCrank() {
        System.out.println("already turned Crank");
        return true;
    }

    /**
     * WinnerState该状态下要发放2颗糖果。
     * 如果糖果没有了,则改变机器的状态到SoldOutState。
     * 如果还有剩余糖果,则改变机器的状态到NoQuarterState。
     */
    @Override
    public void dispense() {
        // 发放一颗糖果
        gumballMachine.releaseBall();
        if (gumballMachine.getCount() == 0) {
            // 如果没有剩余糖果了,就改变机器的状态为售罄。
            gumballMachine.setState(gumballMachine.getSoldOutState());
        } else {
            // 再发放一颗糖果
            gumballMachine.releaseBall();
            if (gumballMachine.getCount() == 0) {
                gumballMachine.setState(gumballMachine.getSoldOutState());
            } else {
                gumballMachine.setState(gumballMachine.getNoQuarterState());
            }
        }
    }
}
package org.dengxl.designpattern.state;

/**
 * 糖果售罄状态
 */
public class SoldOutState extends State {
    GumballMachine gumballMachine;

    /**
     * 通过构造器得到GumballMachine的引用,然后将它记录在实例变量中
     * @param gumballMachine
     */
    public SoldOutState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("there's no gumball");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("there's no gumball");
    }

    @Override
    public boolean turnCrank() {
        System.out.println("there's no gumball");
        return false;
    }

    @Override
    public void dispense() {
        System.out.println("there's no gumball");
    }
}

package org.dengxl.designpattern.state;

import lombok.Getter;
import lombok.Setter;

public class GumballMachine {

    /**
     * 没有25分钱状态
     */
    @Getter
    private State noQuarterState;

    /**
     * 有25分钱状态
     */
    @Getter
    private State hasQuarterState;

    /**
     * 发放糖果状态
     */
    @Getter
    private State soldState;

    /**
     * 售罄状态
     */
    @Getter
    private State soldOutState;

    /**
     * 赢家状态
     */
    @Getter
    private State winnerState;

    /**
     * 当前状态,默认是售罄
     */
    @Setter
    State state = soldOutState;

    /**
     * 剩余糖果数量
     */
    @Getter
    @Setter
    private int count = 0;

    /**
     * 所有的状态对象都是在构造器中创建并赋值的。
     * @param numberBalls
     */
    public GumballMachine(int numberBalls) {
        this.noQuarterState = new NoQuarterState(this);
        this.hasQuarterState = new HasQuarterState(this);
        this.soldState = new SoldState(this);
        this.soldOutState = new SoldOutState(this);

        this.count = numberBalls;
        if (this.count > 0) {
            this.state = noQuarterState;
        }
    }

    public void insertQuarter() {
        state.insertQuarter();
    }

    public void ejectQuarter() {
        state.ejectQuarter();
    }

    public void turnCrank() {
        if (state.turnCrank()) {
            // 请注意,我们不需要在GumballMachine中准备一个dispense的动作方法,因为这只是一个内部的动作。
            // 用户不可以直接要求机器发放糖果。
            state.dispense();
        }
    }

    public void releaseBall() {
        System.out.println("release ball");
        if (count != 0) {
            count -= 1;
        }
    }

    public void refill(int numberBalls) {
        this.count = numberBalls;
        this.state = noQuarterState;
    }

    @Override
    public String toString() {
        return "state=" + state + ", count=" + count;
    }
}

package org.dengxl.designpattern.state;

public class GumballMachineTest {

    public static void main(String[] args) {
        GumballMachine gumballMachine = new GumballMachine(5);
        System.out.println(gumballMachine);
        
        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();
        
        System.out.println(gumballMachine);
        
        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();
        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();
        
        System.out.println(gumballMachine);
    }
}

设计模式之间的对比

二、总结要点

  • 状态模式允许一个对象基于内部状态而拥有不同的行为。
  • 和程序状态机(PSM)不同,状态模式用类代表状态。
  • Context会将行为委托给当前状态对象。
  • 通过将每个状态封装进一个类,我们把以后需要做的任何改变局部化了。
  • 状态模式和策略模式有相同的类图,但是它们的意图不同。
  • 策略模式通常会用行为或算法来配置Context类。
  • 状态模式允许Context随着状态的改变而改变行为。
  • 状态转换可以由State类或Context类控制。
  • 使用状态模式通常会导致设计中类的数目大量增加。
  • 状态类可以被多个Context实例共享。

三、应用场景

1. 订单处理流程

在电子商务应用中,订单处理流程可能包括多个状态(如:新建、待支付、已支付、已发货、已完成、已取消等)。使用状态模式可以使得每个状态的行为封装在不同的状态类中。

2. 用户会话管理

在Web应用中,用户会话管理可以根据用户的不同状态(如登录、注销、会话超时等)来改变其行为。