赞
踩
在工作时遇到了这样一个需求:
控制消毒柜:
1. 当柜门打开时,关闭消毒,并重置已消毒时间;
2. 当柜门关闭时,打开消毒,并在指定时间(如30分钟)后关闭消毒;
在接到这个需求后,我第一反应就是有很多的状态流转,可以试一试状态模式。本文通过这样一个真实的公司需求,讲解设计模式中的状态模式,以及他的应用----状态机。
简单的来说,状态模式就是,为一个对象赋予一个属性,这个属性代表了对象的当前状态,而对象可以根据这个状态来执行不同的动作。
设计模式的实现离不开抽象,状态模式也一样,可以分为以下3步实现:
听完上述讲解的状态模式,大家可能会觉得这个模式用处不大,不知道什么时候用。然而真正的工作中,需求往往都是十分复杂的,单纯的一个状态模式是没法解决什么问题的,只能说这是一种抽象、封装的思想。一个需求的实现肯定不是一个设计模式就可以解决的,一定是多个设计模式协同使用。 接下来给大家介绍一下状态机,它是状态模式的一种应用。
简单来说,状态机就是一组状态的集合,它维护了这一组状态之间的流转关系。
状态机一共有三种角色,即事件、状态、动作
状态机就是维护了一组状态,所以状态这个角色是必不可少的。
状态之间会发生流转,而事件就是触发这个流转的线索。
一个状态可以是平白无故的也可以是经过了一定的动作之后才流转到另一个状态
在分析状态机如何使用的时候,可以按照以下步骤:
定义三个角色,即把整个需求中,会出现的状态,会发生的事件,需要执行的动作全部列出来
画出状态流转图,流转图需要理清楚:
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"; @Override public 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"; @Override public 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; } @Override public void run() { super.run(); while (!isInterrupt) { //10秒查一次所有柜门的状态 SystemClock.sleep(10 * 1000); // 查询每个柜门的结果,返回关门的locker LockerManager.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; } @Override public 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); } } } }
再实现的过程中,我们发现设计模式并不是按照模板来实现的,而是根据真实的场景定制的。但是设计模式的根本却是不变的,也就是找到角色,找到共性,最后进行抽离。
而且我们可以发现,再对事件和状态进行构建时,我们用了简单工厂,当然我们还可以用构建器、用单例,都没有问题。再事件分发上,是不是也很像一个观察者模式,所以设计模式的使用是非常灵活,而且需要组合使用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。