设计模式:状态模式与状态机
文章目录
- 前言
- 状态模式
- 实现
- 状态机
- 概念
- 状态
- 事件
- 动作
- 实现技巧
- 实战
- 分析
- 总结
前言
在工作时遇到了这样一个需求:
控制消毒柜:
1. 当柜门打开时,关闭消毒,并重置已消毒时间;
2. 当柜门关闭时,打开消毒,并在指定时间(如30分钟)后关闭消毒;
在接到这个需求后,我第一反应就是有很多的状态流转,可以试一试状态模式。本文通过这样一个真实的公司需求,讲解设计模式中的状态模式,以及他的应用----状态机。
状态模式
简单的来说,状态模式就是,为一个对象赋予一个属性,这个属性代表了对象的当前状态,而对象可以根据这个状态来执行不同的动作。
实现
设计模式的实现离不开抽象,状态模式也一样,可以分为以下3步实现:
- 定义基类BaseState:它包含了doExecute方法;
- 不同的状态,都继承BaseState,并实现doExecute方法,代表该状态下能够执行的动作;
- 为一个对象,增加BaseState属性,这样就可以通过调用BaseState.doExecute()实现不同的状态执行不同的行为。
状态机
听完上述讲解的状态模式,大家可能会觉得这个模式用处不大,不知道什么时候用。然而真正的工作中,需求往往都是十分复杂的,单纯的一个状态模式是没法解决什么问题的,只能说这是一种抽象、封装的思想。一个需求的实现肯定不是一个设计模式就可以解决的,一定是多个设计模式协同使用。 接下来给大家介绍一下状态机,它是状态模式的一种应用。
概念
简单来说,状态机就是一组状态的集合,它维护了这一组状态之间的流转关系。
状态机一共有三种角色,即事件、状态、动作
状态
状态机就是维护了一组状态,所以状态这个角色是必不可少的。
事件
状态之间会发生流转,而事件就是触发这个流转的线索。
动作
一个状态可以是平白无故的也可以是经过了一定的动作之后才流转到另一个状态
实现技巧
在分析状态机如何使用的时候,可以按照以下步骤:
定义三个角色,即把整个需求中,会出现的状态,会发生的事件,需要执行的动作全部列出来
画出状态流转图,流转图需要理清楚:
a. 每个状态能被什么事件触发?
b. 每个状态被触发事件以后会流转到什么状态?
c. 每个状态在触发事件时,会执行什么动作?
实战
在熟悉了上面的基础知识以后,接下来就根据开头我们遇到的真实案例,使用状态模式来分析并实现它。
分析
首先我们按照上述状态机的分析方法,来分析这个需求:
定义角色:
状态: 未消毒状态、正在消毒状态、已消毒状态事件: 关门事件、开门事件、消毒完成事件
动作: 打开消毒、关闭消毒
以上三个角色的定义代码如下
public enum LockerEventEnum {/*** 关门事件----> 开消毒 并启动计时*/CLOSE_DOOR,/*** 开门事件----> 关消毒*/OPEN_DOOR,/*** 消毒时长已到事件 ----> 关消毒*/TIME_OUT }public enum LockerStateEnum {/*** 未消毒状态,接收关门事件*/UN_UV,/*** 消毒中状态,接收开门、消毒超时事件*/UVING,/*** 已消毒状态,接收开门事件*/HAS_UV }/*** 动作基类** @author Hu*/ public abstract class BaseTransition {/*** 动作的具体实现* @param event 触发该动作的事件* @param machine* @return 动作执行完以后,下一个状态*/public abstract LockerState doExecute(LockerEvent event, LockerMachine machine); }/*** 关闭消毒动作** @author Hu*/ public class CloseUvTransition extends BaseTransition {private final String TAG = "CloseUvTransition";@Overridepublic LockerState doExecute(LockerEvent state, LockerMachine machine) {// 关闭消毒具体实现类UvManager.getInstance().close(machine);// 根据触发该动作的事件,返回对应的下一个状态switch (state.getEvent()) {case OPEN_DOOR: {return StateFactory.createEvent(LockerStateEnum.UN_UV);}case TIME_OUT: {return StateFactory.createEvent(LockerStateEnum.HAS_UV);}default:return StateFactory.createEvent(LockerStateEnum.UN_UV);}} }/*** 打开消毒任务** @author hu*/ public class OpenUvTransition extends BaseTransition {private final String TAG = "OpenUvTransition";@Overridepublic LockerState doExecute(LockerEvent state, LockerMachine machine) {// 设置开始消毒的当前时间machine.setLastUvTime(System.currentTimeMillis());// 将该对象增加轮询队列中,判断是否到达指定消毒时间MachineManager.getInstance().addUvIngMachine(machine);// 打开消毒具体实现类UvManager.getInstance().open(machine);return StateFactory.createEvent(LockerStateEnum.UVING);} }
画出状态图:
a. 每个状态能被什么事件触发?b. 每个状态被触发事件以后会流转到什么状态?
c. 每个状态在触发事件时,会执行什么动作?
以上三个问题:
未消毒状态:能够被关门事件触发,并执行打开消毒动作,流转为正在消毒状态消毒中状态:能够被开门事件触发,并执行关闭消毒动作,流转为未消毒转态能够被消毒完成事件触发,并执行关闭消毒动作,流转为已完成消毒状态已完成消毒状态:能够被开门事件触发,并执行关闭消毒动作,流转为未消毒转态
分析完以后问题以后,我们的思路就挺清晰了,接下来我们看代码的实现。
状态类:
/*** 锁状态** @author Hu*/
public class LockerState {/*** 状态属性*/private LockerStateEnum state;public LockerStateEnum getState() {return state;}/*** 该状态能够响应的事件*/private List<LockerEvent> eventList;/*** 构造函数* @param state*/public LockerState(LockerStateEnum state) {this.state = state;eventList = new ArrayList<>();}/*** 初始化该状态能够响应什么事件;* @param events*/public void declareEvents(LockerEvent... events){eventList.addAll(Arrays.asList(events));}/*** 判断事件是否可以被该状态响应** @param event* @return*/public boolean canHandle(LockerEvent event) {if (eventList != null && eventList.size() > 0) {for (LockerEvent item : eventList) {if (item.getEvent() == event.getEvent()) {return true;}}return false;} else {return false;}}}
状态类包含了能够响应的事件列表,在程序开始时,需要提前通过declareEvents()定义好该状态类能够响应的事件,当事件发生的时候通过canHandle()方法判断是否该状态能够响应
事件类:
/*** 事件类** @author Hu*/
public class LockerEvent {/*** 事件枚举*/private LockerEventEnum event;/*** 当发生该事件对应的动作*/private BaseTransition transition;public LockerEventEnum getEvent() {return event;}public BaseTransition getTransition() {return transition;}/*** 构造函数需要完成:* 1. 定义该事件的执行动作* @param event*/public LockerEvent(LockerEventEnum event, BaseTransition transition) {this.event = event;this.transition = transition;}
}
事件类具有BaseTransition 属性,代表该事件被响应时,能够执行的动作
不论是事件还是状态,都需要提前定义好,这里我通过一个简单工厂来实现事件和状态的构造
/*** 状态工厂** @author Hu*/
public class StateFactory {/*** todo 将LockerState 弄成单例形式,节省空间*/public static LockerState createEvent(LockerStateEnum stateEnum) {LockerState state = new LockerState(stateEnum);switch (stateEnum) {case UN_UV: {//未消毒状态 ,开门事件和关门事件state.declareEvents(EventFactory.createEvent(LockerEventEnum.CLOSE_DOOR));break;}case UVING: {//正在消毒状态响应,开门事件和消毒完毕事件state.declareEvents(EventFactory.createEvent(LockerEventEnum.TIME_OUT),EventFactory.createEvent(LockerEventEnum.OPEN_DOOR));break;}case HAS_UV: {//消毒完成状态,响应开门事件state.declareEvents(EventFactory.createEvent(LockerEventEnum.OPEN_DOOR));break;}default:}return state;}}/*** 事件工厂** @author Hu*/
public class EventFactory {/*** todo LockerEvent 弄成单例形式,节省空间*/public static LockerEvent createEvent(LockerEventEnum eventEnum) {switch (eventEnum) {case OPEN_DOOR: {//开门事件return new LockerEvent(LockerEventEnum.OPEN_DOOR, new CloseUvTransition());}case CLOSE_DOOR: {//关门事件return new LockerEvent(LockerEventEnum.CLOSE_DOOR, new OpenUvTransition());}case TIME_OUT: {//消毒完成事件return new LockerEvent(LockerEventEnum.TIME_OUT, new CloseUvTransition());}default:return new LockerEvent(LockerEventEnum.TIME_OUT, new CloseUvTransition());}}}
最后我们需要书写持有状态属性的对象,以及管理这些对象的状态机类。
/*** 锁状态机** @author Hu*/
public class LockerMachine {/*** 锁实体*/private Locker locker;public Locker getLocker() {return locker;}/*** 当前的状态*/private LockerState curState;public LockerState getCurState() {return curState;}/*** 消毒开始的时间*/private long lastUvTime;public long getLastUvTime() {return lastUvTime;}public void setLastUvTime(long lastUvTime) {this.lastUvTime = lastUvTime;}/*** 构造函数需要执行的操作:* <p>* 1. 初始化locker数据* <p>* 2. 初始化当前状态为UN_UV*/public LockerMachine(Locker locker) {this.locker = locker;this.lastUvTime = 0;curState = StateFactory.createEvent(LockerStateEnum.UN_UV);}/*** 响应事件:* <p>* 1. 判断事件是否可以被响应* <p>* 2. 通过事件执行对应的动作** @param event*/public void execute(LockerEvent event) {if (curState.canHandle(event)) {//可以处理该事件curState = event.getTransition().doExecute(event, this);}}
}
每个对象都具有当前这个状态的属性,以及响应事件的方法execute(), 当事件来临时通过curState.canHandle判断是否能够响应,再通过event.getTransition().doExecute执行动作。
/*** 状态机控制类,作用为:* <p>* 1. 维护所有的lockerMachine,事件分发* <p>* 2. 对正在消毒的lockerMachine计时,分发超时关闭消毒事件** @author Hu*/
public class MachineManager {private final String TAG = "MachineManager";/*** 单例*/private volatile static MachineManager instance;private MachineManager() {machines = new ArrayList<>();uvIngMachines = new ArrayList<>();machinesThread = new LoopMachinesThread();machinesThread.setInterrupt(false);queryLockerThread = new LoopQueryLockerThread();queryLockerThread.setInterrupt(false);}public static MachineManager getInstance() {if (instance == null) {instance = new MachineManager();}return instance;}/*** 通过locker查询响应的LockerMachine */private LockerMachine getLockerMachine(Locker locker) {for (LockerMachine item : machines) {if (item.getLocker().equals.locker) {return item;}}return null;}/*** 维护所有的LockerMachine*/private List<LockerMachine> machines;/*** 维护正在消毒的LockerMachine*/private List<LockerMachine> uvIngMachines;/*** 轮询uvIngMachines线程,判断是否已到关消毒时间*/private LoopMachinesThread machinesThread;/*** 轮询Locker线程,判断是否有关门的locker*/private LoopQueryLockerThread queryLockerThread;/*** 通过Locker初始化machines,并启动检测线程,开始进入状态流转** @param list Locker数据*/public void initData(List<Locker> list) {for (Locker item : list) {machines.add(new LockerMachine(item));}
//if (!machinesThread.getInterrupt()) {machinesThread.setInterrupt(false);machinesThread.start();}if (!queryLockerThread.getInterrupt()) {queryLockerThread.setInterrupt(false);queryLockerThread.start();}}/*** 停止状态流转*/public void stop() {machinesThread.setInterrupt(true);queryLockerThread.setInterrupt(true);}/*** 向指locker分发事件*/public void dispatchEvent(LockerEvent event, Locker locker) {LockerMachine lockerMachine = getLockerMachine(locker);if (lockerMachine != null) {lockerMachine.execute(event);}}/*** 向正在消毒的队列添加值* @param machine*/public void addUvIngMachine(LockerMachine machine) {if (uvIngMachines != null) {uvIngMachines.add(machine);}}class LoopQueryLockerThread extends Thread {private boolean isInterrupt = false;public void setInterrupt(boolean isInterrupt) {this.isInterrupt = isInterrupt;}public boolean getInterrupt() {return isInterrupt;}@Overridepublic void run() {super.run();while (!isInterrupt) {//10秒查一次所有柜门的状态SystemClock.sleep(10 * 1000);// 查询每个柜门的结果,返回关门的lockerLockerManager.query((result) -> {//分发关门事件for (Locker i : result) {dispatchEvent(EventFactory.createEvent(LockerEventEnum.CLOSE_DOOR),i);}});}}}class LoopMachinesThread extends Thread {private boolean isInterrupt = false;public void setInterrupt(boolean isInterrupt) {this.isInterrupt = isInterrupt;}public boolean getInterrupt() {return isInterrupt;}@Overridepublic void run() {super.run();while (!isInterrupt) {SystemClock.sleep(10 * 1000);List<LockerMachine> temp = new ArrayList<>();// 轮询正在消毒的柜子for (LockerMachine item : uvIngMachines) {// 判断是否完成消毒if (System.currentTimeMillis() - item.getLastUvTime() >= ParamConfig.maxUvTime) {//1. 分发消毒完毕事件dispatchEvent(EventFactory.createEvent(LockerEventEnum.TIME_OUT),item.getLocker().section,item.getLocker().port);//2. 分发完毕移除该项temp.add(item);}}uvIngMachines.remove(temp);}}}
}
总结
再实现的过程中,我们发现设计模式并不是按照模板来实现的,而是根据真实的场景定制的。但是设计模式的根本却是不变的,也就是找到角色,找到共性,最后进行抽离。
而且我们可以发现,再对事件和状态进行构建时,我们用了简单工厂,当然我们还可以用构建器、用单例,都没有问题。再事件分发上,是不是也很像一个观察者模式,所以设计模式的使用是非常灵活,而且需要组合使用。
设计模式:状态模式与状态机相关推荐
- 设计模式 - 状态模式(状态机)
有限状态机(英语:finite-state machine,缩写:FSM) 有限状态机又称有限状态自动机(英语:finite-state automation,缩写:FSA),简称状态机,是表示有限个 ...
- 设计模式-状态模式实现状态机
1.概述 在软件开发过程中,应用程序可能会根据不同的情况作出不同的处理.最直接的解决方案是将这些所有可能发生的情况全都考虑到.然后使用if... ellse语句来做状态判断来进行不同情况的 ...
- 趣谈设计模式 | 状态模式(State):如何实现游戏中的状态切换?
文章目录 案例:马里奥积分竞赛 有限状态机 分支逻辑法 查表法 状态模式 状态模式与策略模式 总结 完整代码与文档 案例:马里奥积分竞赛 喜欢马里奥的小伙伴们都应该知道,前不久马里奥为了庆祝35周年, ...
- Java 有限状态机 (设计模式——状态模式)
Java 有限状态机 (设计模式--状态模式) 编写代码的时候,有时会遇见较为复杂的swith...case...和if...else...语句.这一刻有时会想到状态机,用有限状态机替换swith.. ...
- Python设计模式-状态模式
Python设计模式-状态模式 代码基于3.5.2,代码如下; #coding:utf-8 #状态模式class state():def writeProgram(self,work):raise N ...
- Java 设计模式——状态模式
概述 很多人在说状态模式的时候总拿策略模式来进行对比,可能他们的类图会有一点类似,可我却不认为他们有多么相像.你可以阅读<Java设计模式--策略模式>这篇博客,并与本文对比,以找到蛛丝马 ...
- 设计模式状态模式uml_UML的完整形式是什么?
设计模式状态模式uml UML:统一建模语言 (UML: Unified Modeling Language) UML is an abbreviation of Unified Modeling L ...
- C++设计模式——状态模式
C++设计模式--状态模式 在实际开发中,我们经常会遇到这种情况:一个对象有多种状态,在每一个状态下,都会有不同的行为.那么在代码中我们经常是这样实现的. 1 2 3 4 5 6 7 8 9 10 ...
- C++设计模式——状态模式(state pattern)
一.原理讲解 别名状态对象(object for state). 1.1意图 允许一个对象在其内部状态改变时改变它的行为.对象看起来似乎修改了它的类. 1.2应用场景 一个对象的行为取决于它的状态,并 ...
最新文章
- spring源码分析之spring jmx
- SQLite中的高级SQL
- Effective C# Item23:避免返回内部类对象的引用
- linux wifi ip,Linux环境下使用WIFI模块:使用DHCP工具动态获得IP地址
- “化鲲为鹏,我有话说”如何用鲲鹏弹性云服务器部署《Python网络爬虫开发环境》
- ip和端口的本质与作用,网络协议栈
- Spark写入MySQL报错乱码+报错
- AC日记——红色的幻想乡 洛谷 P3801
- js 对Array的补充
- [hdu 1003] Max Sum
- 游戏王怪兽胶囊Android,游戏王怪兽胶囊GB是什么?
- android 倒计时 动画下载,我的倒计时软件下载-我的倒计时 安卓版v1.1.50-PC6安卓网...
- Tomcat 发布时war解压
- 最近发现一款超牛的记单词软件,分享一下
- 一文读懂 12种卷积方法
- MySQL批量造数据
- jquery禁止鼠标右键 原生js禁止鼠标右键
- Cannot find name ‘console‘. Do you need to change your target library?ging the ‘lib‘ compiler option
- 控制反转 vs 依赖注入
- win7 从网络访问此计算机',在里面把guest用户组添加上,大白菜修复win7系统没有权限访问网络资源的办法...
热门文章
- 3.1 计算机视觉的发展和卷积神经网络概要(百度架构师手把手带你零基础实践深度学习原版笔记系列)
- 单机版Solr的中文分词器solr IK下载和配置、拓展词典、停用词典
- Java工程师学习指南(中级篇)
- 微信键盘 0.9 内测发布:丝滑流畅,快来体验~
- Argon扩展板智能温控调速开关控制
- 01 什么是webgl
- Keras深度学习(4)-回归问题之预测房价
- 英飞凌 —— 一文弄懂IGBT驱动
- 瑞萨e2studio(22)----移植兆易创新SPI Nor Flash之GD25Q64Flash
- Apache Dubbo已不再局限于Java语言