一、为什么要了解状态机模式?
事出有因,年前在进行代码review的时候 有个语音通话的功能使用了状态机,
当时对状态模式一头的雾水。
只记得协程的内部原理也是用到了状态机。为此状态模式需要深入理解学习一下。
二、什么是状态机模式?
状态机模式官方的介绍:
当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。
说人话:
一个对象有多个状态,因某些行为(事件)使其状态发生了转换,但是状态之间的转换没有对外暴露。这就是状态模式要解决的问题。
状态模式的核心:封装
状态的变更引起行为的变更
如果还是不理解就看下面的代码演示。
先说一个经常被其他文章使用到的一个样例:
1、实现一个电梯的基本功能
一个电梯可以进行下面的4动作:停止、运行、开门、关门。
电梯接口
public interface ILift {
//首先电梯门开启动作
public void open();
//电梯门可以开启,那当然也就有关闭了
public void close();
//电梯要能上能下
public void run();
//电梯还要能停下来
public void stop();
}
电梯实现类
public class Lift implements ILift {
//电梯门关闭
public void close() {
System.out.println("电梯门关闭...");
}
//电梯门开启
public void open() {
System.out.println("电梯门开启...");
}
//电梯开始运行起来
public void run() {
System.out.println("电梯上下运行起来...");
}
//电梯停止
public void stop() {
System.out.println("电梯停止了...");
}
}
调用
public static void main(String[] args) {
ILift lift = new Lift();
//首先是电梯门开启,人进去
lift.open();
//然后电梯门关闭
lift.close();
//再然后,电梯运行起来,向上或者向下
lift.run();
//最后到达目的地,电梯停下来
lift.stop();
}
太简单的程序了。现实情况往往比较复杂如下
2、完善电梯功能添加条件限制
现实情况电梯门不是随时可以开启的,有相应的前提:
具体来说:特定状态下才能做特定事
● 敞门状态
按了电梯上下按钮,电梯门开,这中间大概有10秒的时间,那就是敞门状态。在这个状态下电梯只能做的动作是关门动作。
● 闭门状态
电梯门关闭了,在这个状态下,可以进行的动作是:开门(我不想坐电梯了)、停止(忘记按路层号了)、运行。
● 运行状态
电梯正在跑,上下窜,在这个状态下,电梯只能做的是停止。
● 停止状态
电梯停止不动,在这个状态下,电梯有两个可选动作:继续运行和开门动作。
因为不同的动作 电梯会进入不同的状态:停止状态、运行状态、开门状态、关门状态。
而且各个状态之间的影响关系是:
开门 | 关门 | 运行 | 停止 | |
---|---|---|---|---|
敞门状态 | ○ | ☆ | ○ | ○ |
闭门状态 | ☆ | ○ | ☆ | ☆ |
运行状态 | ○ | ○ | ○ | ☆ |
停止状态 | ☆ | ○ | ☆ | ○ |
电梯状态和动作对应表(○表示不允许,☆表示允许动作)
因此对电梯添加了4种状态
public interface ILift {
//电梯的4个状态
public final static int OPENING_STATE = 1; //敞门状态
public final static int CLOSING_STATE = 2; //闭门状态
public final static int RUNNING_STATE = 3; //运行状态
public final static int STOPPING_STATE = 4; //停止状态
//设置电梯的状态
public void setState(int state);
//首先电梯门开启动作
public void open();
//电梯门可以开启,那当然也就有关闭了
public void close();
//电梯要能上能下,运行起来
public void run();
//电梯还要能停下来
public void stop();
}
电梯具体实现类添加限制
public class Lift implements ILift {
private int state;
public void setState(int state) {
this.state = state;
}
//电梯门关闭
public void close() {
//电梯在什么状态下才能关闭
switch(this.state){
case OPENING_STATE: //可以关门,同时修改电梯状态
this.closeWithoutLogic();
this.setState(CLOSING_STATE);
break;
case CLOSING_STATE: //电梯是关门状态,则什么都不做
//do nothing;
break;
case RUNNING_STATE: //正在运行,门本来就是关闭的,也什么都不做
//do nothing;
break;
case STOPPING_STATE: //停止状态,门也是关闭的,什么也不做
//do nothing;
break;
}
}
//电梯门开启
public void open() {
//电梯在什么状态才能开启
switch(this.state){
case OPENING_STATE: //闭门状态,什么都不做
//do nothing;
break;
case CLOSING_STATE: //闭门状态,则可以开启
this.openWithoutLogic();
this.setState(OPENING_STATE);
break;
case RUNNING_STATE: //运行状态,则不能开门,什么都不做
//do nothing;
break;
case STOPPING_STATE: //停止状态,当然要开门了
this.openWithoutLogic();
this.setState(OPENING_STATE);
break;
}
}
//电梯开始运行起来
public void run() {
switch(this.state){
case OPENING_STATE: //敞门状态,什么都不做
//do nothing;
break;
case CLOSING_STATE: //闭门状态,则可以运行
this.runWithoutLogic();
this.setState(RUNNING_STATE);
break;
case RUNNING_STATE: //运行状态,则什么都不做
//do nothing;
break;
case STOPPING_STATE: //停止状态,可以运行
this.runWithoutLogic();
this.setState(RUNNING_STATE);
}
}
//电梯停止
public void stop() {
switch(this.state){
case OPENING_STATE: //敞门状态,要先停下来的,什么都不做
//do nothing;
break;
case CLOSING_STATE: //闭门状态,则当然可以停止了
this.stopWithoutLogic();
this.setState(CLOSING_STATE);
break;
case RUNNING_STATE: //运行状态,有运行当然那也就有停止了
this.stopWithoutLogic();
this.setState(CLOSING_STATE);
break;
case STOPPING_STATE: //停止状态,什么都不做
//do nothing;
break;
}
}
//纯粹的电梯关门,不考虑实际的逻辑
private void closeWithoutLogic(){
System.out.println("电梯门关闭...");
}
//纯粹的电梯开门,不考虑任何条件
private void openWithoutLogic(){
System.out.println("电梯门开启...");
}
//纯粹的运行,不考虑其他条件
private void runWithoutLogic(){
System.out.println("电梯上下运行起来...");
}
//单纯的停止,不考虑其他条件
private void stopWithoutLogic(){
System.out.println("电梯停止了...");
}
}
程序有点长,但是还是很简单的,就是在每一个接口定义的方法中使用switch…case来判断它是否符合业务逻辑,然后运行指定的动作。
通过代码功能演示发现了不少的问题:
- 电梯实现类Lift有点长
- 扩展性非常差劲
- 非常规状态无法实现
因此我们需要对此代码优化处理使用:状态模式
三、代码演示
状态模式类图:
状态模式中有三种角色:
State – 抽象状态角色
接口或抽象类,负责对象状态定义,并且封装环境以实现状态切换。
ConcreteState – 具体状态角色
每一个具体状态必须完成两个职责:
本状态的行为管理
趋势状态处理
通俗地说,就是本状态下要做的事情,以及本状态如何过渡到其他状态。
Context – 环境角色(machine)
定义客户端需要的接口,并且负责具体状态的切换
1、创建 抽象状态角色 – State
public abstract class State {
// 定义一个环境角色 ,提供子类访问
protected StateMachine machine;
// 设置环境角色
public void setState(StateMachine machine) {
this.machine = machine;
}
// 行为1
public abstract void action1();
// 行为2
public abstract void action2();
}
2、创建具体状态角色 - ConcreteState1 、ConcreteState2
public class ConcreteState1 extends State{
@Override
public void action1() {
// 转换到本状态下,必须要处理的逻辑
}
@Override
public void action2() {
// 设置当前状态 为state2
super.machine.setCurrentState(StateMachine.STATE2);
// 过渡到state2状态,由StateMachine实现
super.machine.action2();
}
}
public class ConcreteState2 extends State{
@Override
public void action1() {
// 设置当前状态为 state1
super.machine.setCurrentState(StateMachine.STATE1);
// 过渡到state状态,由Context实现
super.machine.action1();
}
@Override
public void action2() {
// 本状态下必须要处理的逻辑
}
}
3、创建环境角色 context – StateMachine
public class StateMachine {
public final static State STATE1 = new ConcreteState1();
public final static State STATE2 = new ConcreteState2();
// 当前状态
private State currentState;
public StateMachine() {
}
// 获取当前状态
public State getCurrentState() {
return currentState;
}
// 设置当前状态
public void setCurrentState(State currentState) {
this.currentState = currentState;
// 切换状态
this.currentState.setState(this);
}
// 行为委托
public void action1(){
this.currentState.action1();
}
public void action2(){
this.currentState.action2();
}
}
环境角色有两个不成文的约束:
- 把状态对象声明为静态常量,有几个状态对象就声明几个静态常量
- 环境角色具有状态抽象角色定义的所有行为,具体执行使用委托方式
public static void main(String[] args) {
//定义环境角色
Context context = new Context();
//初始化状态
context.setCurrentState(new ConcreteState1());
//行为执行
context.handle1();
context.handle2();
}
我们已经隐藏了状态的变化过程,它的切换引起了行为的变化。对外来说,我们只看到行为的发生改变,而不用知道是状态变化引起的
四、应用场景
在电梯添加行为条件限制后的代码比较繁琐,因此借助状态模式代码模板对比写下代码:
1、创建 抽象状态角色 – State
public abstract class Status {
// 定义一个环境角色 ,提供子类访问
public LiftStatusMachine machine;
// 设置环境角色
public void setLiftStateMachine(LiftStatusMachine machine) {
this.machine = machine;
}
// 行为 方法
public abstract void open();
public abstract void close();
public abstract void running();
public abstract void stop();
}
2、创建具体状态角色 - ConcreteState1 、ConcreteState2
这里的ConcreteState分别是:CloseStatus、OpenStatus、RunningStatu、StopStatus
CloseStatus
public class CloseStatus extends Status {
// 电梯关门状态 可以打开
@Override
public void open() {
// 设置电梯为 打开状态
super.machine.setCurStatus(LiftStatusMachine.sOpenStatus);
// 打开电梯
super.machine.open();
}
@Override
public void close() {
Log.println("电梯关闭");
}
@Override
public void running() {
// 设置电梯为 运行状态
super.machine.setCurStatus(LiftStatusMachine.sRunningStatus);
// 电梯运行
super.machine.running();
}
@Override
public void stop() {
// 设置电梯为 停止状态
super.machine.setCurStatus(LiftStatusMachine.sStopStatus);
// 电梯停止
super.machine.stop();
}
}
OpenStatus
public class OpenStatus extends Status {
@Override
public void open() {
Log.println("电梯打开 ");
}
@Override
public void close() {
super.machine.setCurStatus(LiftStatusMachine.sCloseStatus);
super.machine.close();
}
@Override
public void running() {
// do nothing
Log.println("error 电梯门打开,无法运行 running !!!");
}
@Override
public void stop() {
// do nothing
Log.println("error 电梯门打开,无法停止 stop !!!");
}
}
RunningStatus
public class RunningStatus extends Status {
@Override
public void open() {
// do nothing
Log.println("error 电梯运行中 无法打开电梯门 open !!! ");
}
@Override
public void close() {
// do nothing
Log.println("error 电梯运行中 无法再关闭电梯门 open !!! ");
}
@Override
public void running() {
Log.println("电梯上下运行中...");
}
@Override
public void stop() {
super.machine.setCurStatus(LiftStatusMachine.sStopStatus);
super.machine.stop();
}
}
StopStatus
public class StopStatus extends Status {
@Override
public void open() {
// 电梯停止状态 可以打开电梯门
super.machine.setCurStatus(LiftStatusMachine.sOpenStatus);
super.machine.open();
}
@Override
public void close() {
// 电梯停止状态 无法关闭电梯门(原本就是关闭的???)
// no nothing
Log.println("error 电梯停止无法再次关闭电梯门!close ");
}
@Override
public void running() {
// 电梯停止状态 可以再次运行
super.machine.setCurStatus(LiftStatusMachine.sRunningStatus);
super.machine.running();
}
@Override
public void stop() {
Log.println("电梯 停止运行");
}
}
3、创建环境角色 context – StateMachine
public class LiftStatusMachine {
/**
* 当前电梯状态
*/
private Status curStatus;
/**
* 初始化所有电梯状态 电梯门打开、电梯门关闭、电梯运行、电梯停止
*/
public final static Status sOpenStatus = new OpenStatus();
public final static Status sCloseStatus = new CloseStatus();
public final static Status sRunningStatus = new RunningStatus();
public final static Status sStopStatus = new StopStatus();
public LiftStatusMachine() {
// 默认电梯是停止状态
curStatus = new StopStatus();
curStatus.setLiftStateMachine(this);
}
public Status getCurStatus(){
return curStatus;
}
public void setCurStatus(Status status) {
curStatus = status;
curStatus.setLiftStateMachine(this);
}
public void open() {
this.curStatus.open();
}
public void close() {
this.curStatus.close();
}
public void running() {
this.curStatus.running();
}
public void stop() {
this.curStatus.stop();
}
}
使用:
public static void main(String[] args) {
LiftStatusMachine machine = new LiftStatusMachine();
machine.open();
machine.stop();
machine.running();
machine.open();
machine.close();
machine.stop();
machine.running();
machine.stop();
machine.open();
machine.close();
machine.close();
}
五、少啰嗦一句话解释状态机模式
六、参考
https://juejin.cn/post/6844904005336825863
https://cloud.tencent.com/developer/article/1625473
https://yuriyshea.com/archives/%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%8A%B6%E6%80%81%E6%9C%BA
https://www.kancloud.cn/sstd521/design/193606