Back
Featured image of post 设计模式-状态模式

设计模式-状态模式

一、模式简介

今天学习的是状态模式,它通过改变对象内部的状态来帮助对象控制自己的行为。也有人说策略模式和状态模式是双胞胎,在出生的时候才分开。状态模式:状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。 下面我们来对状态模式进行详细解析。

二、模式详解

在提起状态模式之前我们先了解一下有限状态机的概念:有限状态机:有限状态自动机 (FSM “finite state machine” 或者FSA “finite state automaton” )是为研究有限内存的计算过程和某些语言类而抽象出的一种计算模型。有限状态自动机拥有有限数量的状态,每个状态可以迁移到零个或多个状态,输入字串决定执行哪个状态的迁移。通过有限状态机的概念,我们可以总结出其主要思想是程序在任意时刻仅可处于几种有限状态中。 在任何一个特定状态中, 程序的行为都不相同, 且可瞬间从一个状态切换到另一个状态。 不过根据当前状态, 程序可能会切换到另外一种状态, 也可能会保持当前状态不变。

下面我们来引入今天的例子:我们想要创造一个糖果机,来达到投钱自动产生糖果的效果,通过下图我们可以看到糖果机的运行步骤:

通过查看我们糖果机的运行图,发现它是不是就是一个有限状态机呢?下面我们来分析:糖果的产出有着如下的几种状态:没有一元钱有一元钱售出糖果糖果售罄这四种状态。而糖果机的整体运行流程也是在这四种状态间来回进行切换的,下面我们来实现糖果机的代码:

  • 糖果机
/**
 * 糖果机
 *
 * @author 张鹏
 * @date 2022/03/20
 */
public class GumballMachine {
    /**创建出四种状态常量**/
    final static int SOLD_OUT=0;
    final static int NO_QUARTER=1;
    final static int HAS_QUARTER=2;
    final static int SOLD=3;
    /**
     * 将状态赋值默认状态
     */
    int state=SOLD_OUT;
    /**
     * 糖果数
     */
    int count=0;
    public GumballMachine(int count){
        this.count=count;
        if (count>0){
            state=NO_QUARTER;
        }
    }

    /**
     * 投入一元钱
     */
    public void insertQuarter(){
        if (state==HAS_QUARTER){
            System.out.println("你已投过钱!");
        }else if (state==NO_QUARTER){
            System.out.println("投钱成功!");
            state=HAS_QUARTER;
        }else if (state==SOLD_OUT){
            System.out.println("你不可以投钱,因为糖果机没有糖果了!");
        }else if (state==SOLD){
            System.out.println("糖果正在产出,请勿投钱");
        }
    }

    /**
     * 退钱
     */
    public void ejectQuarter(){
        if (state==HAS_QUARTER){
            System.out.println("退钱成功!");
            state=NO_QUARTER;
        }else if (state==NO_QUARTER){
            System.out.println("您没有投钱,不能退钱!");
        }else if (state==SOLD_OUT){
            System.out.println("你不可以退钱,因为糖果机没有糖果了!");
        }else if (state==SOLD){
            System.out.println("糖果正在产出,已无法退钱");
        }
    }

    /**
     * 转动曲柄
     */
    public void turnCrank(){
    if (state==HAS_QUARTER){
        System.out.println("正在转动--");
        state=SOLD;
        dispense();
    }else if (state==NO_QUARTER){
        System.out.println("您没有投钱!!");
    }else if (state==SOLD){
        System.out.println("休想骗过机器拿到两次糖果");
    }else if (state==SOLD_OUT){
        System.out.println("糖果已售罄,无法转动");
    }
    }
    public void dispense(){
        if (state==SOLD){
            System.out.println("给您一个糖果!");
            count--;
            if (count==0){
                state=SOLD_OUT;
            }else {
                count=NO_QUARTER;
            }
        }else if (state==NO_QUARTER){
            System.out.println("您没有投钱");
        }else if (state==SOLD_OUT){
            System.out.println("不该发生这一行为!");
        }else if (state==HAS_QUARTER){
            System.out.println("不该发生这一行为");
        }
    }
}

通过糖果机代码的实现,可以发现我们使用了大量的if和else来对状态进行判断,这让我们的糖果机代码看起来十分的复杂。并且想要新增一种状态时需要改变大量的业务代码不利于维护:比如糖果机有十分之一的概率,在转动曲柄时产生两次糖果我们称作幸运儿状态。这时就要使用我们的状态模式去解决这一问题,下面我们先来观察下状态模式的类图:

Context是一个类,它拥有一些内部的状态。在我们例子中糖果机就是这个类

State 接口(也可以是抽象类)定义了一个所有具体状态的共同接口,任何状态都实现这个相同的接口

ConcreteState具体状态,处理Context的请求

下面我们来使用状态模式实现糖果机,并加入新的幸运儿状态:

  • 糖果机
/**
 * 糖果机
 *
 */
@Data
public class GumballMachine {
    /**创建出五种状态常量**/
    State soldOutState;
    State noQuarterState;
    State hasQuarterState;
    State soldState;
    State winnerState;

    /**
     * 将状态赋值默认状态
     */
    State state=soldOutState;
    /**
     * 糖果数
     */
    int count=0;
    public GumballMachine(int count){
        this.soldOutState=new SoldOutState(this);
        this.noQuarterState=new NoQuarterState(this);
        this.hasQuarterState=new HasQuarterState(this);
        this.soldState=new SoldState(this);
        this.winnerState=new WinnerState(this);
        this.count=count;
        if (count>0){
            state=noQuarterState;
        }
    }

    /**
     * 投入一元钱
     */
    public void insertQuarter(){
    state.insertQuarter();
    }

    /**
     * 退钱
     */
    public void ejectQuarter(){
    state.ejectQuarter();
    }

    /**
     * 转动曲柄
     */
    public void turnCrank(){
   	if (state.turnCrank()){
         state.dispense();
     }
    }

    /**
     * 允许这个方法设置状态
     *
     */
    public void setState(State state){
     this.state=state;
    }

    /**
     * 减少糖果数
     */
    public void releaseBall(){
        if (count!=0){
            System.out.println("给您一个糖果");
            count--;
        }
    }
}
  • 状态类
/**
 * 状态类
 *
 */
public interface State {
    /**
     *投入钱
     */
    void insertQuarter();

    /**
     * 退钱!
     */
    void ejectQuarter();

    /**
     * 转动曲柄
     */
    boolean turnCrank();

    /**
     * 产出糖果
     */
    void dispense();
}
  • 未投钱状态
public class NoQuarterState implements State{
    GumballMachine gumballMachine;
    public NoQuarterState(GumballMachine gumballMachine){
        this.gumballMachine=gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("成功投入一元!");
        gumballMachine.setState(gumballMachine.hasQuarterState);
    }

    @Override
    public void ejectQuarter() {
        System.out.println("您还没有投钱");
    }

    @Override
    public boolean turnCrank() {
        System.out.println("您还没有投钱");
        return false;
    }

    @Override
    public void dispense() {
        System.out.println("无法操作");
    }
}
  • 已投钱状态
public class HasQuarterState implements State{
    Random random = new Random(System.currentTimeMillis());
    GumballMachine gumballMachine;
    public HasQuarterState(GumballMachine gumballMachine){
        this.gumballMachine=gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("您不能再投钱了!");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("退钱成功!");
        gumballMachine.setState(gumballMachine.noQuarterState);
    }

    @Override
    public boolean turnCrank() {
        System.out.println("正在转动--");
        int winner = random.nextInt(10);
        if (winner==10&&(gumballMachine.getCount()>1)){
            //设置大赢家状态
            gumballMachine.setState(gumballMachine.winnerState);
        }else {
            gumballMachine.setState(gumballMachine.soldState);
        }
        return true;
    }

    @Override
    public void dispense() {
        System.out.println("无法操作");
    }
}
  • 产出糖果状态
public class SoldState implements State{
    GumballMachine gumballMachine;
    public SoldState(GumballMachine gumballMachine){
        this.gumballMachine=gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("正在产出糖果,无法投钱");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("正在产出糖果,无法退钱");
    }

    @Override
    public boolean turnCrank() {
        System.out.println("正在产出糖果,不要转动两次!");
        return false;
    }

    @Override
    public void dispense() {
    gumballMachine.releaseBall();
    if (gumballMachine.getCount()>0){
        gumballMachine.setState(gumballMachine.noQuarterState);
    }else {
        System.out.println("糖果已售罄");
        gumballMachine.setState(gumballMachine.soldOutState);
    }
    }
}
  • 糖果售罄状态
public class SoldOutState implements State{
    GumballMachine gumballMachine;
    public SoldOutState(GumballMachine gumballMachine){
        this.gumballMachine=gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("糖果已经售罄了,无法投钱");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("您没有投钱,无法退钱");
    }

    @Override
    public boolean turnCrank() {
        System.out.println("您还没有投钱");
        return false;
    }

    @Override
    public void dispense() {
        System.out.println("无法操作");
    }
}
  • 幸运儿状态
public class WinnerState implements State{
    GumballMachine gumballMachine;
    public WinnerState(GumballMachine gumballMachine){
        this.gumballMachine=gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("正在产出糖果,无法投钱");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("正在产出糖果,无法退钱");
    }

    @Override
    public boolean turnCrank() {
        System.out.println("正在产出糖果,不要转动两次!");
        return false;
    }

    @Override
    public void dispense() {
        System.out.println("您是幸运儿!!!");
        gumballMachine.releaseBall();
        if (gumballMachine.getCount()==0){
            gumballMachine.setState(gumballMachine.soldOutState);
        }else {
            gumballMachine.releaseBall();
            if (gumballMachine.getCount()>0){
            gumballMachine.setState(gumballMachine.noQuarterState);
            }else {
            gumballMachine.setState(gumballMachine.soldOutState);
            }
        }
    }
}

这就是状态模式的实现,通过我们前面说状态模式的好处和状态模式的类图,我们不难发现状态模式和策略模式是比较相似的,那么它们有什么不同呢?

  • 从意义上:策略模式封装了一组相关算法,它允许Client在运行时使用可互换的行为;状态模式帮助一个类在不同的状态显示不同的行为。
  • 策略的改变由Client完成;而状态的改变,由Context或状态自己。
  • 在状态模式中,通常每个状态通过持有Context的引用,来实现状态转移;但是每个策略都不持有Context的引用,它们只是被Context使用。

三、适用场景

  • 如果对象需要根据自身当前状态进行不同行为,同时状态的数量非常多且与状态相关的代码会频繁变更的话,可使用状态模式。

  • 如果某个类需要根据成员变量的当前值改变自身行为,从而需要使用大量的条件语句时,可使用该模式。

  • 当相似状态和基于条件的状态机转换中存在许多重复代码时,可使用状态模式。