五种状态设计模式:
- 终极钩子模式
- 提醒者模式
- 延迟事件模式
- 正交组件模式
- 转换到历史模式
1.终极钩子模式
俗语:老爸对儿子说,你可以按你的特殊方式去做事,但如果你不做,我会做。
目的:公共的处理功能放到父状态,并可以在子状态中重载,以实现特殊的功能。为什么叫终极钩子,因为如果子状态不处理,总会在父状态中得到处理,是个终极的处理。
问题:父状态提供公共一致的事件处理方式,如在GUI设计中,提供一致的界面样式,为所有的子状态提供默认的一致界面,如果需要,子状态也可以重载默认的事件处理方式。
解决方案:使用差异化编程,公共的事件处理放到父状态中,子状态中重载特殊行为。子状态(specific)中的A事件处理重载了父状态A事件处理,子状态中的A事件处理就是特殊的事件处理。如果某个子状态不处理A,则在父状态中处理A。
图1.终极钩子模式
2.提示器模式
俗语:我闲的时候,你通知我做,我就做;我在做事的时候,你就不能通知我了,等我闲时再说。
目的:产生一个事件,并发送给自身,以使状态图拓扑更加灵活。
问题:在状态建模时,一些相关的功能耦合太强,用提示器模式,可以解耦。如定时查询传感器功能与处理数据功能,当查询到传感器接完数据时,通过发送一个提示事件让处理数据功能进行处理。
图2. 两个功能模块
解决方案:两个功能设计为一个层次状态机结构,查询功能设计到父状态中,处理功能设计到子状态中,用一个提示器事件(DATA_READY),提示处理功能来处理查询传感器的得到的数据。
图3. 提示器模式
可以用提示器模式把一个长任务分解为多个小任务来执行。
3.延迟事件模式
俗语:我很忙,有事我先记下,我闲时,我再按我记下的去做。
目的:通过修改事件的顺序来简化状态机。用于处理事务。
问题: 当前正在处理一个事务时,因事务是原子性的,不可分割的,这时要是来了新的事件,可能会破坏事务处理的原子性。如ATM终端。
解决方案: 在处理事务时,接收事件并保存到本地队列中,当本事务处理完时再从本地队列中召回保存的事件。
图4. 延迟事件模式
4.正交组件模式
俗语:虽然你是你,我是我,但你听我的没错。
目的:把状态机做为一个组件来使用,把原来正交区域的独立关系转化为主-从关系。
问题: 许多对象包含相对独立的具有状态行为的部分,在UML状态图中实现方式是,把这种松散关联的功能放到独立的正交区域,而采用正交区域的解决方法,成本较高。如闹钟的功能实现。
解决方案: 使用对象合成来代替正交区域,把行为独立的两部分分成两个状态机,其中一个包含另一个。正交区域的两个对象,使一个对象成为容器,包含另一个组件对象,由容器对象负责对组件对象的初始化及调用。通过直接向组件派发事件,容器可以主动触发组件初始化和状态转换;而组件是通过发送事件给容器来通知容器的。
图5. 正交组件模式
5.转换到历史模式
俗语: 我在工作中,当有更重要的事时,我马上去做,做完后,我会回到我被中断工作前的状态。
目的:从某个组合状态转换出来时,记住最近的活动子状态,在返回时,还返回到这个活动子状态。
问题:在高层组合状态处理事件时,常常需要处理一些更重要的事件,当处理完时,系统必须返回组合状态最近的子状态。
解决方案: 在组合状态机的属性中加一个状态函数变量xxx_history,在状态机进入某状态时,保存当前状态到这个状态函数变量中。在退出组合状态后返回时,按照保存在状态函数变量值,转换到最后退出组合状态前的状态。
图6.转换到历史模式
参考:
【1】Miro Samek《UML状态图的实用C/C++设计---嵌入式系统的事件驱动型编程技术》第二版