【设计模式】学状态模式?学状态机√

608 阅读6分钟

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

什么是状态模式(State)

概念

状态模式(State Pattern)属于行为型模式,定义:一个对象的行为由其内部的状态决定

要想学习状态模式,那就一定要提到状态机了,状态机又是什么?状态机就是列举出一个东西的多种状态,研究这几种状态的切换行为,然后绘制成一个可以连通的状态图

从事Java五年状态

这里以门闸为例,在广州地铁里,我们过完安检后,想要进入到地铁内部的话就需要刷卡过门闸,这里的闸门就有很多种状态,未刷卡前是关闭状态,我们无法通过闸门;刷卡后会有一个中间状态:处理中状态,这个状态,我们已经刷了卡,但机器还在处理,没有把门打开,我们也无法通过;处理完成后就变成了打开状态,我们可以通过,通过后就变成了关闭状态。在这个过程中,闸门有三种状态:关闭、处理中、打开,我们需要跟闸门进行交互的行为:刷卡、通过,下面我画一个状态图:

image-20210614221609478

我在学习的时候发现,这个状态模式的实现跟策略模式的几乎一毛一样。

可以说状态模式是策略模式的衍生版,虽然他们的实现是一模一样的,但是解决的问题却是不一样的。策略模式是把算法封装起来,让这些算法可以相互替换,提高代码的复用性;而状态模式完完全全就是围绕着状态机来的,提供了一个状态机的实现方式,状态模式才能说减少了因判断当前状态的if-else和switch的使用,后面会做一下演示。

小黄鸡带气球骑车

优点

  1. 状态对象封装了状态的转换规则,减少了判断语句的使用。
  2. 不同的环境对象可以共用状态对象,减少了对象的个数。
  3. 符合单一职责原则。分离出不同的状态和行为,让每个状态类的职责更加清晰,有利于系统的扩展。

缺点

  1. 没有遵守开放封闭原则。如果新增了新的状态,想要使用这个状态,就要在旧的状态类中加上转换到新状态的逻辑。
  2. 设计与实现比较复杂。

原则

“+”代表遵守,“-”代表不遵守或者不相关

原则开放封闭单一职责迪米特里氏替换依赖倒置接口隔离合成复用
-+--+--

适用场景

  1. 当一个对象的行为取决于它的状态。
  2. 需要大量判断或分支语句的情况。

如何实现

想要实现状态模式,需要以下三样东西:

  1. 环境抽象类(Abstract Context):它需要定义与用户交互的方法,并且有一个状态对象,所有的方法都委托给这个状态对象来实现
  2. 环境(Context)类:实现环境抽象类,并且扩展其他功能。
  3. 状态(State)接口/抽象类:定义状态的行为,这些行为和环境类的一致,还需要实现状态切换的逻辑。
  4. 具体状态(Concrete State)类:实现抽象状态接口/抽象类,实现状态转换逻辑。

类图

状态模式的结构图

例子

这里以前面的闸门为例,闸门的三种状态:关闭、处理中、打开,跟闸门进行交互的行为:刷卡、通过。

不用状态模式的例子

这里简单写一下不用状态模式实现会是怎么样的:

/**
 * 不使用状态模式实现的闸门
 * Created on 2021/6/15.
 *
 * @author xuxiaobai
 */
public class NoUserStatePatternGate {
​
    //当前状态,初始状态为关闭状态
    private String currentState=CLOSE_STATE;
​
    //关闭状态
    private static String  CLOSE_STATE="closed";
    //打开状态
    private static String  OPEN_STATE="open";
    //处理中
    private static String  PROCESSING="processing";
​
    /**
     * 刷卡
     */
    public void pay(){
        if (this.currentState.equals(CLOSE_STATE)){
            //当前是关闭,刷卡应该转为处理中
            System.out.println("刷卡成功,正在处理");
            //转为正在处理状态
            this.currentState=PROCESSING;
            Random random=new Random();
            //模拟出现故障
            if (random.nextBoolean()){
                processSuccess();
            }else {
                processFail();
            }
            return;
        }
​
        if (this.currentState.equals(PROCESSING)){
            System.out.println("请稍等。。正在处理");
            return;
        }
​
        if (this.currentState.equals(OPEN_STATE)){
            System.out.println("闸门已经打开了,请通过");
            return;
        }
​
    }
    /**
     * 通过
     */
    public void pass(){
        if (this.currentState.equals(CLOSE_STATE)){
            System.out.println("无法通过,请刷卡");
            return;
        }
​
        if (this.currentState.equals(PROCESSING)){
            System.out.println("请稍等。。正在处理");
            return;
        }
​
        if (this.currentState.equals(OPEN_STATE)){
            System.out.println("已通过,闸门关闭");
            this.currentState=CLOSE_STATE;
            return;
        }
​
​
​
    }
​
    /**
     * 处理失败
     */
    private void processFail(){
        System.out.println("处理失败!");
        //转回关闭状态
        this.currentState=CLOSE_STATE;
    }
​
    /**
     * 处理成功
     */
    private void processSuccess(){
        System.out.println("处理成功!");
        //转到打开状态
        this.currentState=OPEN_STATE;
    }
​
}

测试:

/**
 * 状态模式测试
 * Created on 2021/6/15.
 *
 * @author xuxiaobai
 */
public class StateTest {
​
​
    public static void main(String[] args) {
        NoUserStatePatternGate gate=new NoUserStatePatternGate();
        gate.pass();
        gate.pay();
        gate.pay();
        gate.pass();
        gate.pay();
        gate.pass();
        gate.pay();
        gate.pass();
        gate.pay();
        gate.pass();
        /**
         * 结果:
         * 无法通过,请刷卡
         * 刷卡成功,正在处理
         * 处理成功!
         * 闸门已经打开了,请通过
         * 已通过,闸门关闭
         * 刷卡成功,正在处理
         * 处理成功!
         * 已通过,闸门关闭
         * 刷卡成功,正在处理
         * 处理失败!
         * 无法通过,请刷卡
         * 刷卡成功,正在处理
         * 处理失败!
         * 无法通过,请刷卡
         */
    }
}

基本功能实现了,减去了支付扣钱这些操作,如果你想实现的话,可以拷贝下去补充。

这里的if-else不是一般的多啊,改成switch会好一点,不过还是会让整个代码变得非常的麻烦,如果以后想要再加新增状态,就只能继续加if-else了,逻辑也非常的混乱,多加几个状态后,可能就不是普通人所能驾驭的了,下面来看看使用状态模式是如何实现的。

使用状态模式

代码有那么一点复杂,加了一个共享状态对象,其他的就是把这些判断当前状态的if-else去掉了,每个if-else的逻辑操作都分散到状态类中了。

类图

这个类图有那么一丢丢乱,没办法,状态模式实现起来有点复杂,不过每个类的职责都很清晰的,结合后面的代码来看。

image-20210615221742141

环境类

环境抽象类

/**
 * 环境抽象类
 * 闸门抽象类
 *
 * Created on 2021/6/15.
 *
 * @author xuxiaobai
 */
public abstract class AbstractGate {
​
​
    /**
     * 闸门状态
     */
    protected AbstractGateState state;
    /**
     * 因为这一部分的代码都是一样的
     * 所以这里写到抽象方法中了
     */
​
    /**
     * 刷卡
     */
    public void pay() {
        this.state.pay(this);
    }
​
    /**
     * 通过
     */
    public void pass() {
        this.state.pass(this);
    }
​
    public AbstractGateState getState() {
        return state;
    }
​
    protected AbstractGate setState(AbstractGateState state) {
        this.state = state;
        return this;
    }
}

具体环境类

/**
 * 环境类
 * 地铁闸门
 * Created on 2021/6/15.
 *
 * @author xuxiaobai
 */
public class SubwayGate extends  AbstractGate{
​
    public SubwayGate(){
        //默认关闭
        this(false);
    }
​
    public SubwayGate(boolean isOpen){
        //可以通过构件方法设置是否开启闸门
        Class stateClass;
        if (isOpen){
            stateClass= OpenState.class;
        }else {
            stateClass=ClosedState.class;
        }
        this.state=AbstractGateState.getState(stateClass);
    }
​
​
}
状态类

状态抽象类

/**
 * 状态抽象类
 * 闸门状态
 * Created on 2021/6/15.
 *
 * @author xuxiaobai
 */
public abstract class AbstractGateState {
​
    private static Map<Class,AbstractGateState> stateMap=new HashMap<>();
​
    /**
     * 刷卡
     */
    public abstract void pay(AbstractGate gate);
​
    /**
     * 通过
     */
    public abstract void pass(AbstractGate gate);
​
    /**
     * 这里写一个空方法
     * 主要是考虑到处理状态是一个中间的瞬时的状态,需要完成一定的处理逻辑
     * 如果只是切换状态的话,就无法触发处理逻辑,环境类只与用户交互两个动作:pay、pass,无法触发“处理中状态”的处理逻辑
     * @throws IllegalAccessException
     */
    public void handle(AbstractGate gate) throws IllegalAccessException {
        throw new IllegalAccessException();
    }
​
​
    /**
     * 用这个方法来获取状态对象,实现状态对象的共用
     * @param stateClass
     * @return
     */
    protected static final AbstractGateState getState(Class stateClass){
        AbstractGateState state = stateMap.get(stateClass);
        //双重校验
        if (state==null){
            synchronized (AbstractGateState.class){
                state = stateMap.get(stateClass);
                if (state==null){
                    try {
                        state= (AbstractGateState) stateClass.newInstance();
                        stateMap.put(stateClass,state);
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return state;
    }
​
}

关闭状态

/**
 * 具体状态类
 * 关闭状态
 * Created on 2021/6/15.
 *
 * @author xuxiaobai
 */
public class ClosedState extends AbstractGateState {
​
    @Override
    public void pay(AbstractGate gate) {
        //当前是关闭,刷卡应该转为处理中
        System.out.println("刷卡成功,正在处理");
        //设置为处理中状态
        AbstractGateState processingState = getState(ProcessingState.class);
        gate.setState(processingState);
        try {
            processingState.handle(gate);
        } catch (IllegalAccessException e) {
​
        }
    }
​
    @Override
    public void pass(AbstractGate gate) {
        System.out.println("无法通过,请刷卡");
    }
}

处理中状态

/**
 * 具体状态类
 * 处理中状态
 * Created on 2021/6/15.
 *
 * @author xuxiaobai
 */
public class ProcessingState extends AbstractGateState  {
​
    //模拟出现故障概率
    private Random random = new Random();
​
    @Override
    public void pay(AbstractGate gate) {
        System.out.println("请稍等。。正在处理");
        return;
    }
​
    @Override
    public void pass(AbstractGate gate) {
        System.out.println("请稍等。。正在处理");
        return;
    }
​
    @Override
    public void handle(AbstractGate gate) throws IllegalAccessException {
        //转为正在处理状态
        if (random.nextBoolean()) {
            System.out.println("处理失败!");
            gate.setState(getState(ClosedState.class));
        } else {
            System.out.println("处理成功!");
            gate.setState(getState(OpenState.class));
        }
    }
​
​
}

打开状态

/**
 *
 * 具体状态类
 * 打开状态
 * Created on 2021/6/15.
 *
 * @author xuxiaobai
 */
public class OpenState extends AbstractGateState {
    @Override
    public void pay(AbstractGate gate) {
        System.out.println("闸门已经打开了,请通过");
        return;
    }
​
    @Override
    public void pass(AbstractGate gate) {
        System.out.println("已通过,闸门关闭");
        //设置为关闭状态
        gate.setState(getState(ClosedState.class));
        return;
​
    }
}
测试
/**
 * 状态模式测试
 * Created on 2021/6/15.
 *
 * @author xuxiaobai
 */
public class StateTest {
​
​
    public static void main(String[] args) {
        AbstractGate gate=new SubwayGate();
        gate.pass();
        gate.pay();
        gate.pay();
        gate.pay();
        gate.pay();
        gate.pass();
        gate.pay();
        gate.pass();
        gate.pay();
        gate.pass();
        gate.pay();
        gate.pass();
        /**
         * 结果:
         * 无法通过,请刷卡
         * 刷卡成功,正在处理
         * 处理成功!
         * 闸门已经打开了,请通过
         * 已通过,闸门关闭
         * 刷卡成功,正在处理
         * 处理成功!
         * 已通过,闸门关闭
         * 刷卡成功,正在处理
         * 处理失败!
         * 无法通过,请刷卡
         * 刷卡成功,正在处理
         * 处理失败!
         * 无法通过,请刷卡
         */
    }
}

这里的处理是否通过是有概率的,所以每次结果都会不一样。

写到这里就结束了,你都看到这了,还不给我点个赞?

我敬你是个点赞狂魔

总结

虽说状态模式是策略模式的衍生版,但它们在使用上不会存在太大的差别,唯一需要注意的就是它们俩的思维逻辑不同,策略模式只关注把算法、逻辑等等封装到一个类中,而状态模式是把算法与逻辑和状态一起封装到一个类中,状态模式是离不开状态的,可以说状态模式包含策略模式。

——————————————————————————————

你知道的越多,不知道的就越多。

如果本文章内容有问题,请直接评论或者私信我。如果觉得我写得还不错的话,点个赞也是对我的支持哦

未经允许,不得转载!