真刀实枪之状态模式

  • 从电梯说起

    • 随着城市的发展,有两样东西的发明在城市的发展中起到非常重要的作用

      1. 汽车:横向发展
      2. 电梯:纵向发展
    • 既然说电梯,那就看看电梯有哪些动作
      1. 开门
      2. 关门
      3. 运行
      4. 停止
    • 好了,动作有了,设计下类图
    • 类图比较通俗易懂,那么先来实现一下,看看有什么问题

      • 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 {// 电梯关闭@Overridepublic void open() {System.out.println("电梯开启~~");}// 电梯关闭@Overridepublic void close() {System.out.println("电梯关闭~~");}// 电梯运行@Overridepublic void run() {System.out.println("电梯运行~~");}// 电梯停止@Overridepublic 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("人到达楼层。");}
        }
        
      • 执行结果

        人来了:
        电梯开启~~
        电梯关闭~~
        电梯运行~~
        电梯停止~~
        人到达楼层。
        
  • 看到这个程序,是不是有点太简单~~非也非也,继续往下,这个程序有什么问题?
    1. 电梯门可以打开,但是不能随便打开吧!
    2. 电梯执行这四个动作应该都有前提条件,在特定的条件下才能做特定的动作
  • 为了解决这些暂时能想到的问题,先来分析电梯有哪些特定的状态
    • 敞门状态:这个状态下,电梯能做的就是关门动作
    • 闭门状态:这个状态下,电梯可以开门、停止、运行
    • 运行状态:这个状态下,电梯只能停止
    • 停止状态:这个状态下,电梯能做运行和开门动作
  • 电梯的状态和动作之间的关系下图来演示:
  • 有了这个前提,那我们的代码中的方法执行前就得有前置条件的判断了,只有满足相应条件才能进行相应的动作

    • 先改类图

    • 代码改动

      • 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;// 电梯的状态// 设置电梯的状态@Overridepublic 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("电梯停止~~");}/* 有逻辑的电梯执行方法 */@Overridepublic 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;}}}@Overridepublic 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;}}}@Overridepublic 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;}}}@Overridepublic 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();}
        }
        
      • 执行结果

        电梯开启~~
        电梯关闭~~
        电梯运行~~
        
  • 程序有了,咱们再来挑挑骨头吧!
    1. 电梯的实现类【Lift】有点长
    2. 扩展性非常差--比如再加几个状态或者方法,变动很大。。。
    3. 非常规状态无法实现:电梯故障
  • 这些问题是实实在在存在的,现在我们换个角度看问题--这个状态是由什么动作产生的,以及这个状态下可以执行哪些动作【可以添加状态接口】

    • 类图

    • 解释: 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 {@Overridepublic void open() {System.out.println("电梯门已经开启~~");}@Overridepublic void close() {// 状态修改super.context.setLiftState(Context.closingState);// 执行动作super.context.getLiftState().close();}@Overridepublic void run() {// 不可执行}@Overridepublic void stop() {// 不可执行}}
        
      • ClosingState

        package zt2;/*** @author kungfu~peng* @data 2017年11月29日* @description*/
        public class ClosingState extends LiftState {@Overridepublic void open() {// 改变状态super.context.setLiftState(Context.openningState);// 执行super.context.getLiftState().open();}@Overridepublic void close() {System.out.println("已经关闭~~");}@Overridepublic void run() {// 设置状态super.context.setLiftState(Context.runningState);// 执行super.context.getLiftState().run();}@Overridepublic 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 {@Overridepublic void open() {System.out.println("不可操作~~");}@Overridepublic void close() {System.out.println("正在运行,已经关闭~~");}@Overridepublic void run() {System.out.println("正在运行~~");}@Overridepublic 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 {@Overridepublic void open() {// 改变状态super.context.setLiftState(Context.openningState);// 执行super.context.getLiftState().open();}@Overridepublic void close() {System.out.println("已经关闭~~");}@Overridepublic void run() {// 设置状态super.context.setLiftState(Context.runningState);// 执行super.context.getLiftState().run();}@Overridepublic 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();}}
        
      • 执行结果

        电梯门已经开启~~
        已经关闭~~
        正在运行~~
        正在运行,无法停止~~
        已经停止~~ //注:上一个操作完之后的三秒之后执行此操作
        
  • 看看我们这段代码解决了哪些问题
    1. 代码太长--通过子类解决,子类的代码都比较短,也取消了switch-case的结构
    2. 不符合开闭原则--增加状态,现在只需在原有的类上增加即可,不用去修改
    3. 不符合迪米特法则--现在是各个状态的单独类,只有与这个状态相关的因素改了,这个类才做相应的修改

状态模式的定义

  • 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;}// 行为1public abstract void handle1();// 行为2public abstract void handle2();}
    
  • ConcreteState1

    package zt3;/*** @author kungfu~peng* @data 2017年11月29日* @description*/
    public class ConcreteState1 extends State {@Overridepublic void handle1() {// 本状态下必须处理的逻辑System.out.println("handle1~~~");}@Overridepublic void handle2() {// 设置当前的状态为state2super.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 {@Overridepublic void handle1() {// 设置当前的状态为state1super.context.setCurrentState(Context.STATE1);// 过渡到state1状态,由Context实现super.context.handle1();}@Overridepublic 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~~~~
    

不成文的规定

  • 把状态对象声明成静态常量,有几个状态对象就声明几个静态常量
  • 环境角色具有状态抽象角色定义的所有行为,具体执行使用委托方式

状态模式的应用

  • 状态模式的优点

    1. 结构清晰--避免了过多的switch-case或者if-else if的使用,避免了程序的复杂性,提高了系统的可维护性
    2. 遵循设计原则--开闭原则、单一职责原则
    3. 封装性非常好
  • 状态模式的缺点
    • 子类会太多【主要的缺点--数据库状态表可以来解决】
  • 使用场景
    1. 行为随着改变状态而改变的场景【权限设计】
    2. 条件、分支判断语句的替代者【switch-case、if-else if】
  • 注意事项
    • 状态模式适用于当某个对象在它的状态发生改变时,它的行为也随着发生比较大的变化,也就是说在行为受约束的情况下可以使用状态模式,而且使用对象的状态最好不超5个

最佳实践

  • 简单状态切换示意图【TCP监听:等待状态、连接状态、断开状态】

  • 复杂状态切换示意图牌【收费网站:普通用户、普通会员、VIP会员、白金用户】
  • 当电梯碰上维修
    • 改变电梯的执行顺序【建造模式+状态模式】
  • 状态机管理【State Machine】
    1. 初始化状态
    2. 挂起状态
    3. 完成状态

声明

  • 摘自秦小波《设计模式之禅》第2版;
  • 仅供学习,严禁商业用途;
  • 代码手写,没有经编译器编译,有个别错误,自行根据上下文改正;

设计模式之禅【状态模式】相关推荐

  1. 【游戏设计模式】之三 状态模式、有限状态机

    转载自:https://blog.csdn.net/poem_qianmo/article/details/52824776 游戏开发过程中,各种游戏状态的切换无处不在.但很多时候,简单粗暴的if e ...

  2. 【游戏设计模式】之三 状态模式、有限状态机 Unity版本实现

    本系列文章由@浅墨_毛星云 出品,转载请注明出处.   文章链接: http://blog.csdn.net/poem_qianmo/article/details/52824776 作者:毛星云(浅 ...

  3. 【游戏设计模式】之三 状态模式 有限状态机 Unity版本实现

     本系列文章由@浅墨_毛星云 出品,转载请注明出处.    文章链接: http://blog.csdn.net/poem_qianmo/article/details/52824776  作者:毛星 ...

  4. 设计模式之略见一斑(状态模式State)

    设计模式中的状态模式相对比较简单,简单的说就是对某个对象的状态进行管理.对象的状态如果的多的话,假如没有对其进行管理,极易造成管理混乱.从而使系统难以维护,所以State模式的意图就是将与状态有关的处 ...

  5. 设计模式学习笔记-状态模式

    引言 使用该设计模式的情况:如在场景中的主角甚至是敌人,有N个不同状态:走路,攻击,待机.....,游戏中,这些状态来回切换.有一个明确的状态划分的情况下,此时,是可以使用Switch...case. ...

  6. 设计模式C++实现 ——状态模式

    软件领域中的设计模式为开发人员提供了一种使用专家设计经验的有效途径.设计模式中运用了面向对象编程语言的重要特性:封装.继承.多态,真正领悟设计模式的精髓是可能一个漫长的过程,需要大量实践经验的积累.最 ...

  7. c语言lr分析器的设计与实现_Python3设计模式四 :状态模式

    状态(state)模式在实现上类似于策略模式,但是它们的目标非常的不同.状态模式呈现的是一个状态转换系统:系统中的对象处在一个特定的状态当中,经过某些操作之后可以抵达另外的状态. 为了实现这一目标,需 ...

  8. 从王者荣耀看设计模式(六.状态模式)

    从王者荣耀看设计模式(状态模式) 一.简介 英雄项羽在敌方英雄的攻击下存在3种不同的状态. 1.在健康生命值下--普通状态,在每次被攻击时,当前生命值=剩余生命值-敌方英雄伤害值 2.在生命值低于某一 ...

  9. 设计模式:(状态模式)

    1.定义 在状态模式(State Pattern)中,类的行为是基于它的状态改变的.这种类型的设计模式属于行为型模式. 在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 co ...

  10. 设计模式之 State(状态模式)通俗理解

    23种设计模式 1 State 模式的定义 不同的状态,不同的行为;或者说,每个状态有着相应的行为. 2 何时使用? 当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了它的类.状态模式 ...

最新文章

  1. UPC2710T高频放大器
  2. asarray java,Java
  3. 回顾线程的竞争机制-轻量级锁
  4. SOL注入——HTTP头部注入(2)(七)
  5. 怎么修改ipv4服务器,如何修改ipv4 wins 服务器地址
  6. 一键对频对讲机好吗_挑战传统,新型对讲机展现独特一面--极蜂智能网络对讲机...
  7. Python习题week2
  8. 利用Crontab为Linux定时备份Mysql数据库
  9. 微信小程序连接本地接口(转)
  10. MySQL将一张表的某些列数据,复制到另外一张表,并且修改某些内容
  11. 《JAVA程序设计教程(第7版)英文版》pdf 附下载链接
  12. 打造史上最容易使用的Tab指示符——Indicator
  13. 想哭的鱼最新QQ伤感日志发布:你不该,不相信我
  14. Gradle实现多渠道打包(不同资源文件打不同的包)
  15. ctf MISC 放松一下吧
  16. Java小白的入门面试笔记--线程局部变量之灵魂四问
  17. 如何选择Python版本2还是3
  18. 程序员十二星座行为大赏
  19. 设计模式六大基本原则
  20. linux命令行怎么结束进程,linux结束进程命令

热门文章

  1. ros2 foxy 报错缺少“diagnostic_updater“,By not providing “Finddiagnostic_updater.cmake“ in CMAKE_MODULE_P
  2. 7 款优秀 Markdown 编辑工具推荐
  3. dlib php,图片人脸检测——Dlib版(四)
  4. 力扣每日一题2021-09-17有效的数独
  5. 验证错误信息jquery validation
  6. DIV根据里面文字自动撑开
  7. 硬盘SMART信息数据结构
  8. EN 15650: 通风口CE认证
  9. 【实践】电商知识图谱构建及搜索推荐场景下的应用.pdf(附下载链接)
  10. 阿迪达斯成立全球首个零售学院,并开设“模拟店铺”