浅析设计模式 - 状态模式

本文目录:

定义

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

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

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

现实世界的模型

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

状态模式有几个关键元素

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

为什么要使用状态模式?

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

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

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

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

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

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

如果我们用 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,不会影响到其他的状态类。各个状态类独立

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

简单设计

环境类

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

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

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

抽象状态类

定义了状态的处理方法

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

具体状态类

实现状态的处理方法

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

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

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

应用实例

续播栏

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

可以得到这样的状态机

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

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

环境类 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 就行了,修改量大幅度降低

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

猜你喜欢

转载自blog.csdn.net/firefile/article/details/80558471