美文网首页
浅析设计模式-状态模式

浅析设计模式-状态模式

作者: RunAlgorithm | 来源:发表于2017-08-21 23:35 被阅读150次

    定义

    状态模式(State Pattern):允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。

    一个对象有多种状态的变化,然后不同的状态能够转换,不同的状态有不同的行为和数据,这时候可以使用状态模式来封装它。状态模式,会对每一种状态创建一个状态类,这些状态类管理当前状态和到下一个状态变换的数据、行为。

    状态模式的重点在于,不同的状态对象中,有一个相同的行为或者事件发生时,如何进行处理,如何切换到其他状态

    现实世界的模型

    比如,人的情绪有状态,喜怒哀乐,不同的状态会随着某些事件而发生改变

    状态模式有几个关键元素

    • 环境,就是那个有状态的对象,不同的状态有不同的行为和数据
    • 命令,环境对象的使用者发出,通知改变状态
    • 状态类,处理命令,去改变环境的行为和数据,也可以让环境进入下一个状态

    为什么要使用状态模式?

    我们在遇到状态切换的时候,如果使用 if else 或者 switch case 等方式类枚举

    每个要做和状态相关的代码,都要把所有状态枚举一遍,这样会导致代码非常冗长

    而且一旦有需求变化,需要对大量的代码进行修改,容易引发很多意想不到的问题

    比如一个环境对象 Context 中有三个状态,A, B, C,然后响应两个命令 cmd1 和 cmd2,我们使用 switch case 来处理

    简单的状态切换规则如下:

    状态模式-简单例子-状态机.png

    如果我们用 if else 的多重条件判断来实现这些状态转换,会有这样的代码:

    public class Context {
        // 某些数据和行为
        ...
        
        enum State {
            A, B, C
        }
        
        private State state;
    
        public void cmd1() {
            switch state:
                case A:
                    // do something
                    state = B;
                    break;
                case B:
                    // do something
                    state = C;
                    break;
                default:
                    break;
        }
    
        public void cmd2() {
            switch state:
                case A:
                    // do something
                    state = C;
                    break;
                case C:
                    // do something
                    state = B;
                    break;
                default:
                    break;
        }
    }
    

    那么问题来了,如果再新增一个状态 D 呢?需要在各个命令方法的枚举中,继续枚举出 D,然后做修改;如果 装填A 要做修改呢,每个命令关于 A 的都要改过去?随便来一个需求,都需要改动许多地方,维护艰难且容易出错

    我们的改进方法,就是建立状态类,把这些和各个状态相关的处理代码,内聚到这些状态类中。这里有三个状态,我们就这个环境中创建三个状态类,StateA,StateB,StateC,由状态类接管当前状态下环境对象的数据和行为变化

    这样子才能更好地应对未来的变化,不论是新增状态、单个状态发生变化、还是新增命令,都可以减少改动量

    public class Context {
        // ------- 一些数据 -------
        ...
        
        private State mState;
        
        // ------- 行为命令 -------
        
        public void cmd1() {
            mState.handle(1);
            
            // 状态切换代码,也可以让各个状态类来做切换
            if (mState instanceof StateA) {
                mState = new StateB();
            } else if (mState instanceof StateB) {
                mState = new StateC();
            }
        }
        
        public void cmd2() {
            mState.handle(2);
            
            if (mState instanceof StateA) {
                mState = new StateC();
            } else if (mState instanceof StateC) {
                mState = new StateB();
            }
        }
        
        // ------- 状态类 -------
        
        ...
    
        private interface State {
            handleCmd(int cmd);
        }
        
        private class StateA {
            public void handleCmd(int cmd) {
                switch(cmd) {
                    case 1:
                        // do somethig
                        break;
                    case 2:
                        // do somethig
                        break;
                    default:
                        break;
                }
            }
        }
        
        private class StateB {
            public void handleCmd(int cmd) {
                switch(cmd) {
                    case 1:
                        // do somethig
                        break;
                    default:
                        break;
                }
            }
        }
        
        ...
        
    }
    

    新增状态 D 呢,再建一个 StateD,把 D 状态关心的环境数据变化和对应的状态切换加入,不用修改其他状态的代码,直接扩展;如果状态 A 发生变化呢,直接修改 StateA,不会影响到其他的状态类。各个状态类独立

    这样子,就把各个状态的行为切换和数据变化内聚到一个个状态类中,每个状态类只关心自己能够响应的命令。某种状态调整,只需要修改该状态类,修改量小;新增状态,也只需要再实现一个状态类,符合开闭原则

    简单设计

    环境类

    持有一个状态实例引用,拥有多个命令方法给外界调用,外界可以通过环境类,调用某些命令方法,来控制状态的变化

    该环境内部有多个其他对象或者数据,可以随着状态的切换而发生变化,产生某些行为

    具体状态的切换,可以在环境类中做,也可以放到具体的状态类中去做

    抽象状态类

    定义了状态的处理方法

    如果状态切换要放到具体的实现类去完成,还可以定义状态切换方法

    具体状态类

    实现状态的处理方法

    那之前的例子来说,我们可以得到这样的类图

    状态模式-简单例子-类图.png

    环境类就是 Context,抽象状态类就是 IState,具体状态类就是 StateA,StateB,StateC

    应用实例

    续播栏

    需求背景:在视频播放详情页中,在视频播放结束的时候,有一个续播提示栏,用来做续播的倒计时提示。而这个倒计时提示有多个状态,随着外界的变化而变化。主要的状态有初始化、重置、开始、结束、取消等。而外界的变化有滑动页面、关闭屏幕、打开新窗口等等

    可以得到这样的状态机

    状态模式-续播栏-状态机.png

    我的实现和那个简单例子基本一样。不过状态的切换代码,没有放在环境类中去实现,我这边直接放到了各个状态类中去执行了。这两种方式都可以。不过最好还是由环境类去做,减少不同状态类中的相互依赖

    环境类 PlayNextViewHolder

    内置一些 UI 对象,这些 UI 对象跟随着状态切换而发生相应的变化。

    抽象状态类 IState

    这里只定义了状态切换方法和一些状态码

    因为状态的处理放到了每个状态类的构造方法中,从而不需要在抽象类中定义 handle 方法。这只是一种处理方式,当然也可以自己定义 handle,在 handle 中做状态的处理。

    同样,状态切换方法这里是放到了具体的子类,也可以放回环境类 PlayNextViewHolder 中,也是可以的

    我这里只是一种实现方式。模式是一种思想,可以有多种实现

    interface IState {
        int STATE_INIT = 0x0;
        int STATE_RESET_YES = 0x1;
        int STATE_RESET_NO = 0x2;
        int STATE_START = 0x3;
        int STATE_STOP = 0x4;
        int STATE_CANCEL = 0x5;
        int STATE_FINISH = 0x6;
        void switchState(IState state);
    }
    

    具体状态类

    这些都是 PlayNextViewHolder 的内部类,这样的话默认持有了 PlayNextViewHolder 的引用,同时可以访问 PlayNextViewHolder 的 UI 对象,根据状态的切换来操作这些 UI

    比如 InitState

        private class InitState implements IState {
    
            public InitState() {
                stopCountDown();
                closePlayNext();
                mStateLogger.d("init");
            }
    
            @Override
            public void switchState(@State int state) {
                switch (state) {
                    case STATE_RESET_YES:
                        mState = new ResetOKState();
                        break;
                    case STATE_RESET_NO:
                        mState = new ResetNOState();
                        break;
                    default:
                        break;
                }
            }
        }
    

    又比如 FinishState

        private class FinishState implements IState {
    
            public FinishState() {
                stopCountDown();
                closePlayNext();
                mStateLogger.d("finish");
            }
    
            @Override
            public void switchState(@State int state) {
                switch (state) {
                    case STATE_RESET_YES:
                        mState = new ResetOKState();
                        break;
                    case STATE_RESET_NO:
                        mState = new ResetNOState();
                        break;
                    default:
                        break;
                }
            }
        }
    

    具体代码就不罗列了,和业务场景有关

    重要的是领会其中对状态模式的应用

    采取了这个模式了,以后对结束状态的某些对象有调整,就不用像开头那样,找到每一个修改对象的方法,一个个改过去。我们只要修改 FinishState 就行了,修改量大幅度降低

    不过,状态模式单纯从代码上进行阅读并不容易,还是要附上状态图,比较好理解

    相关文章

      网友评论

          本文标题:浅析设计模式-状态模式

          本文链接:https://www.haomeiwen.com/subject/uoqkdxtx.html