真刀实枪之状态模式
-
从电梯说起
- 随着城市的发展,有两样东西的发明在城市的发展中起到非常重要的作用
- 汽车:横向发展
- 电梯:纵向发展
- 既然说电梯,那就看看电梯有哪些动作
- 开门
- 关门
- 运行
- 停止
- 好了,动作有了,设计下类图
-
类图比较通俗易懂,那么先来实现一下,看看有什么问题
-
ILift
package com.peng.zt; /** * @author kungfu~peng * @data 2017年11月28日 * @description */ public interface ILift { // 电梯开启 public void open(); // 电梯关闭 public void close(); // 电梯运行 public void run(); // 电梯停止 public void stop(); }
-
Lift
package com.peng.zt; /** * @author kungfu~peng * @data 2017年11月28日 * @description */ public class Lift implements ILift { // 电梯关闭 @Override public void open() { System.out.println("电梯开启~~"); } // 电梯关闭 @Override public void close() { System.out.println("电梯关闭~~"); } // 电梯运行 @Override public void run() { System.out.println("电梯运行~~"); } // 电梯停止 @Override public void stop() { System.out.println("电梯停止~~"); } }
-
Client
package com.peng.zt; /** * @author kungfu~peng * @data 2017年11月28日 * @description */ public class Client { public static void main(String[] args) { ILift lift = new Lift(); // 人来了,开启电梯 System.out.println("人来了:"); lift.open(); // 然后电梯门关闭 lift.close(); // 然后让电梯运行起来 lift.run(); // 最后人到达目的地,停止电梯 lift.stop(); System.out.println("人到达楼层。"); } }
-
执行结果
人来了: 电梯开启~~ 电梯关闭~~ 电梯运行~~ 电梯停止~~ 人到达楼层。
-
- 随着城市的发展,有两样东西的发明在城市的发展中起到非常重要的作用
- 看到这个程序,是不是有点太简单~~非也非也,继续往下,这个程序有什么问题?
- 电梯门可以打开,但是不能随便打开吧!
- 电梯执行这四个动作应该都有前提条件,在特定的条件下才能做特定的动作
- 为了解决这些暂时能想到的问题,先来分析电梯有哪些特定的状态
- 敞门状态:这个状态下,电梯能做的就是关门动作
- 闭门状态:这个状态下,电梯可以开门、停止、运行
- 运行状态:这个状态下,电梯只能停止
- 停止状态:这个状态下,电梯能做运行和开门动作
- 电梯的状态和动作之间的关系下图来演示:
-
有了这个前提,那我们的代码中的方法执行前就得有前置条件的判断了,只有满足相应条件才能进行相应的动作
- 先改类图
-
代码改动
-
ILift
package com.peng.zt; /** * @author kungfu~peng * @data 2017年11月28日 * @description */ public interface ILift { public static final int OPENING_STATE = 1;// 敞门状态 public static final int CLOSING_STATE = 2;// 闭门状态 public static final int RUNNING_STATE = 3;// 运行状态 public static final int STOPPING_STATE = 4;// 停止状态 // 设置电梯的状态 public void setState(int state); // 电梯开启 public void open(); // 电梯关闭 public void close(); // 电梯运行 public void run(); // 电梯停止 public void stop(); }
-
LIft
package com.peng.zt; /** * @author kungfu~peng * @data 2017年11月28日 * @description */ public class Lift implements ILift { private int state;// 电梯的状态 // 设置电梯的状态 @Override public void setState(int state) { this.state = state; } /* 无逻辑的电梯执行方法 */ // 电梯关闭 private void openWithoutLogic() { System.out.println("电梯开启~~"); } // 电梯关闭 private void closeWithoutLogic() { System.out.println("电梯关闭~~"); } // 电梯运行 private void runWithoutLogic() { System.out.println("电梯运行~~"); } // 电梯停止 private void stopWithoutLogic() { System.out.println("电梯停止~~"); } /* 有逻辑的电梯执行方法 */ @Override public void open() { // 电梯在什么状态下才能开启 switch (this.state) { case OPENING_STATE: {// 现在电梯为开启状态,不能再开启 break; } case CLOSING_STATE: {// 现在电梯为关闭状态,可以开启,记得改变状态 this.openWithoutLogic(); this.setState(OPENING_STATE); break; } case RUNNING_STATE: {// 现在电梯为运行状态,这个时候不允许开门 break; } case STOPPING_STATE: {// 现在电梯为停止状态,门本来就是关闭的,当然可以开启 this.openWithoutLogic(); this.setState(OPENING_STATE); break; } } } @Override public void close() { // 电梯在什么状态下才能关闭 switch (this.state) { case OPENING_STATE: {// 现在电梯为开启状态,可以关闭,别忘了修改电梯的状态 this.closeWithoutLogic(); this.setState(CLOSING_STATE); break; } case CLOSING_STATE: {// 现在电梯为关闭状态,不能再关闭了 break; } case RUNNING_STATE: {// 现在电梯为运行状态,门本来就是关闭的,不需要在关闭了 break; } case STOPPING_STATE: {// 现在电梯为停止状态,门本来就是关闭的,不需要在关闭了 break; } } } @Override public void run() { // 电梯在什么状态下才能运行 switch (this.state) { case OPENING_STATE: {// 现在电梯为开启状态,不可以运行 break; } case CLOSING_STATE: {// 现在电梯为关闭状态,可以运行 this.runWithoutLogic(); this.setState(RUNNING_STATE); break; } case RUNNING_STATE: {// 现在电梯为运行状态, break; } case STOPPING_STATE: {// 现在电梯为停止状态,可以运行 this.runWithoutLogic(); this.setState(RUNNING_STATE); break; } } } @Override public void stop() { // 电梯在什么状态下才能停止 switch (this.state) { case OPENING_STATE: {// 现在电梯为开启状态,不能直接停止 break; } case CLOSING_STATE: {// 现在电梯为关闭状态,可以停止 this.stopWithoutLogic(); this.setState(STOPPING_STATE); break; } case RUNNING_STATE: {// 现在电梯为运行状态,不可以停止 break; } case STOPPING_STATE: {// 现在电梯为停止状态,不需要再停止了 break; } } } }
-
Client
package com.peng.zt; /** * @author kungfu~peng * @data 2017年11月28日 * @description */ public class Client { public static void main(String[] args) { ILift lift = new Lift(); // 电梯门的初始状态应该是停止状态 lift.setState(Lift.STOPPING_STATE); // 开启电梯 lift.open(); // 然后关闭电梯门 lift.close(); // 电梯运行起来 lift.run(); // 到达目的地,停止电梯 lift.stop(); } }
-
执行结果
电梯开启~~ 电梯关闭~~ 电梯运行~~
-
- 先改类图
- 程序有了,咱们再来挑挑骨头吧!
- 电梯的实现类【Lift】有点长
- 扩展性非常差--比如再加几个状态或者方法,变动很大。。。
- 非常规状态无法实现:电梯故障
-
这些问题是实实在在存在的,现在我们换个角度看问题--这个状态是由什么动作产生的,以及这个状态下可以执行哪些动作【可以添加状态接口】
- 类图
- 解释: LiftState:在这之中,声明了一个受保护的类型Context变量,这个是串联各个状态的封装类,封装的目的很明显,就是电梯对象内部状态的变化不被调用类知晓,也就是迪米特法则,并且还定义了四个具体的实现类,承担的是状态产生以及状态间的转换过渡。
-
代码
-
LiftState
package zt2; /** * @author kungfu~peng * @data 2017年11月29日 * @description */ public abstract class LiftState { // 定义一个环境角色,也就是封装状态的变化引起的功能变化 protected Context context; public void setContext(Context context) { this.context = context; } // 电梯开启 public abstract void open(); // 电梯关闭 public abstract void close(); // 电梯运行 public abstract void run(); // 电梯停止 public abstract void stop(); }
-
OpenningState
package zt2; /** * @author kungfu~peng * @data 2017年11月29日 * @description 敞门状态 */ public class OpenningState extends LiftState { @Override public void open() { System.out.println("电梯门已经开启~~"); } @Override public void close() { // 状态修改 super.context.setLiftState(Context.closingState); // 执行动作 super.context.getLiftState().close(); } @Override public void run() { // 不可执行 } @Override public void stop() { // 不可执行 } }
-
ClosingState
package zt2; /** * @author kungfu~peng * @data 2017年11月29日 * @description */ public class ClosingState extends LiftState { @Override public void open() { // 改变状态 super.context.setLiftState(Context.openningState); // 执行 super.context.getLiftState().open(); } @Override public void close() { System.out.println("已经关闭~~"); } @Override public void run() { // 设置状态 super.context.setLiftState(Context.runningState); // 执行 super.context.getLiftState().run(); } @Override public void stop() { // 设置状态 super.context.setLiftState(Context.stoppingState); // 执行 super.context.getLiftState().stop(); } }
-
RunningState
package zt2; /** * @author kungfu~peng * @data 2017年11月29日 * @description */ public class RunningState extends LiftState { @Override public void open() { System.out.println("不可操作~~"); } @Override public void close() { System.out.println("正在运行,已经关闭~~"); } @Override public void run() { System.out.println("正在运行~~"); } @Override public void stop() { System.out.println("正在运行,无法停止~~"); // 运行三秒钟,然后结束 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // 设置状态 super.context.setLiftState(Context.stoppingState); // 执行 super.context.getLiftState().stop(); } }
-
StoppingState
package zt2; /** * @author kungfu~peng * @data 2017年11月29日 * @description */ public class StoppingState extends LiftState { @Override public void open() { // 改变状态 super.context.setLiftState(Context.openningState); // 执行 super.context.getLiftState().open(); } @Override public void close() { System.out.println("已经关闭~~"); } @Override public void run() { // 设置状态 super.context.setLiftState(Context.runningState); // 执行 super.context.getLiftState().run(); } @Override public void stop() { System.out.println("已经停止~~"); } }
-
Context
package zt2; /** * @author kungfu~peng * @data 2017年11月29日 * @description */ public class Context { // 定义出所有的电梯状态 public final static OpenningState openningState = new OpenningState(); public final static ClosingState closingState = new ClosingState(); public final static RunningState runningState = new RunningState(); public final static StoppingState stoppingState = new StoppingState(); // 定义一个当前电梯的状态 private LiftState liftState; public LiftState getLiftState() { return liftState; } public void setLiftState(LiftState liftState) { this.liftState = liftState; // 把当前的环境通知到各个实现类 this.liftState.setContext(this); } // 电梯开启 public void open() { this.liftState.open(); } // 电梯关闭 public void close() { this.liftState.close(); } // 电梯运行 public void run() { this.liftState.run(); } // 电梯停止 public void stop() { this.liftState.stop(); } }
-
Client
package zt2; /** * @author kungfu~peng * @data 2017年11月29日 * @description */ public class Client { public static void main(String[] args) { Context context = new Context(); context.setLiftState(new ClosingState()); context.open(); context.close(); context.run(); context.stop(); } }
-
执行结果
电梯门已经开启~~ 已经关闭~~ 正在运行~~ 正在运行,无法停止~~ 已经停止~~ //注:上一个操作完之后的三秒之后执行此操作
-
- 类图
- 看看我们这段代码解决了哪些问题
- 代码太长--通过子类解决,子类的代码都比较短,也取消了switch-case的结构
- 不符合开闭原则--增加状态,现在只需在原有的类上增加即可,不用去修改
- 不符合迪米特法则--现在是各个状态的单独类,只有与这个状态相关的因素改了,这个类才做相应的修改
状态模式的定义
- Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.(当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类)
- 状态模式的核心是封装,状态的变更引起了行为的变更,从外部看起来就像这个对象对应的类发生了改变一样
状态模式的通用类图
- State:抽象状态角色--接口或抽象类,负责对象状态定义,并且封装环境角色以实现状态转换
- ConcreteState:具体状态角色--每一个具体状态必须完成两个职责,本状态的行为管理以及趋向状态处理,通俗的说,就是本状态下要做的事情,以及 本状态如何过渡到其他状态
- Context:定义客户端需要的接口,并且负责具体状态的切换
通用代码
- 状态模式相对来说比较复杂,它提供了一种对物质运动的另一个观察视角,通过状态变更促使行为的变化,就类似水的状态变更一样,一碗水的初始状态是液态,通过加热转变为气态,状态的改变同时引起了体积的扩大,然后就产生了一个新的行为:鸣笛或顶起壶盖,瓦特就是这么发明的蒸汽机的
-
State
package zt3; /** * @author kungfu~peng * @data 2017年11月29日 * @description */ public abstract class State { // 定义一个环境角色,提供子类访问 protected Context context; // 设置环境角色 public void setContext(Context context) { this.context = context; } // 行为1 public abstract void handle1(); // 行为2 public abstract void handle2(); }
-
ConcreteState1
package zt3; /** * @author kungfu~peng * @data 2017年11月29日 * @description */ public class ConcreteState1 extends State { @Override public void handle1() { // 本状态下必须处理的逻辑 System.out.println("handle1~~~"); } @Override public void handle2() { // 设置当前的状态为state2 super.context.setCurrentState(Context.STATE2); //过渡到state2状态,由Context实现 super.context.handle2(); } }
-
ConcreteState2
package zt3; /** * @author kungfu~peng * @data 2017年11月29日 * @description */ public class ConcreteState2 extends State { @Override public void handle1() { // 设置当前的状态为state1 super.context.setCurrentState(Context.STATE1); // 过渡到state1状态,由Context实现 super.context.handle1(); } @Override public void handle2() { // 本状态下必须处理的逻辑 System.out.println("handle2~~~~"); } }
-
Context
package zt3; /** * @author kungfu~peng * @data 2017年11月29日 * @description */ public class Context { // 定义状态 public final static State STATE1 = new ConcreteState1(); public final static State STATE2 = new ConcreteState2(); // 当前状态 private State CurrentState; // 获取当前状态 public State getCurrentState() { return CurrentState; } // 设置当前状态 public void setCurrentState(State currentState) { CurrentState = currentState; this.CurrentState.setContext(this); } // 行为委托 public void handle1() { // 切换状态 this.CurrentState.handle1(); } public void handle2() { // 切换状态 this.CurrentState.handle2(); } }
-
Client
package zt3; /** * @author kungfu~peng * @data 2017年11月29日 * @description */ public class Client { public static void main(String[] args) { // 定义环境角色 Context context = new Context(); // 初始化状态 context.setCurrentState(new ConcreteState1()); // 行为执行 context.handle1(); context.handle2(); } }
-
执行结果
handle1~~~ handle2~~~~
不成文的规定
- 把状态对象声明成静态常量,有几个状态对象就声明几个静态常量
- 环境角色具有状态抽象角色定义的所有行为,具体执行使用委托方式
状态模式的应用
- 状态模式的优点
- 结构清晰--避免了过多的switch-case或者if-else if的使用,避免了程序的复杂性,提高了系统的可维护性
- 遵循设计原则--开闭原则、单一职责原则
- 封装性非常好
- 状态模式的缺点
- 子类会太多【主要的缺点--数据库状态表可以来解决】
- 使用场景
- 行为随着改变状态而改变的场景【权限设计】
- 条件、分支判断语句的替代者【switch-case、if-else if】
- 注意事项
- 状态模式适用于当某个对象在它的状态发生改变时,它的行为也随着发生比较大的变化,也就是说在行为受约束的情况下可以使用状态模式,而且使用对象的状态最好不超5个
最佳实践
- 简单状态切换示意图【TCP监听:等待状态、连接状态、断开状态】
- 复杂状态切换示意图牌【收费网站:普通用户、普通会员、VIP会员、白金用户】
- 当电梯碰上维修
- 改变电梯的执行顺序【建造模式+状态模式】
- 状态机管理【State Machine】
- 初始化状态
- 挂起状态
- 完成状态
声明
- 摘自秦小波《设计模式之禅》第2版;
- 仅供学习,严禁商业用途;
- 代码手写,没有经编译器编译,有个别错误,自行根据上下文改正;