赞
踩
事出有因,年前在进行代码review的时候 有个语音通话的功能使用了状态机,
当时对状态模式一头的雾水。
只记得协程的内部原理也是用到了状态机。为此状态模式需要深入理解学习一下。
状态机模式官方的介绍:
当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。
说人话:
一个对象有多个状态,因某些行为(事件)使其状态发生了转换,但是状态之间的转换没有对外暴露。这就是状态模式要解决的问题。
状态模式的核心:封装
状态的变更引起行为的变更
如果还是不理解就看下面的代码演示。
先说一个经常被其他文章使用到的一个样例:
一个电梯可以进行下面的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();
}
太简单的程序了。现实情况往往比较复杂如下
现实情况电梯门不是随时可以开启的,有相应的前提:
具体来说:特定状态下才能做特定事
● 敞门状态
按了电梯上下按钮,电梯门开,这中间大概有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来判断它是否符合业务逻辑,然后运行指定的动作。
通过代码功能演示发现了不少的问题:
因此我们需要对此代码优化处理使用:状态模式
状态模式类图:
状态模式中有三种角色:
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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。