定义
状态模式(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 就行了,修改量大幅度降低
不过,状态模式单纯从代码上进行阅读并不容易,还是要附上状态图,比较好理解
网友评论