传媒公司网站设计,企业网站 建设过程,贸易公司网站设计,手机微网站开发教程重修设计模式-行为型-状态模式
先了解一下状态机的概念#xff0c;状态机是软件编程中对一种状态场景的抽象表达#xff0c;构成状态机三要素是#xff1a;状态#xff08;State#xff09;、事件#xff08;Event#xff09;、动作#xff08;Action#xff09;状态机是软件编程中对一种状态场景的抽象表达构成状态机三要素是状态State、事件Event、动作Action事件也称为转移条件事件驱动状态的转移并触发对应的动作其中动作的触发不是必须的。
状态机是一种抽象概念而状态模式是状态机的一种编码实现方式其实还有分支判断法和图表法可以实现状态机。
以电商中订单系统为例订单有待付款待发货待收货已完成和已取消状态而且每个状态还要有特定的事件才能驱动状态的转移和动作触发比如待付款状态的订单由付款和取消事件驱动订单到待发货和已取消状态不响应发货事件待发货状态订单只由发货和取消事件驱动到下一状态而且取消后还要触发退款的动作用一个图来表示这个关系 订单是一种非常典型的状态机场景这种场景的状态、事件和动作都是可以预见的编码时可以先表达出状态机的三要素
//订单状态
enum class OrderState(val value: Int, val desc: String) {WAIT_PAYMENT(0, 待付款),WAIT_SHIPMENT(1, 待发货),WAIT_RECEIPT(2, 待收货),COMPLETED(3, 已完成),CANCELLED(4, 已取消)
}//触发动作
object ActionGroup {fun moneyToPlatform() {println(行为付款给平台...)}fun moneyToSeller() {println(行为金额打给商家...)}fun moneyToBuyer() {println(行为金额退还给买家...)}
}//状态机
class OrderStateMachine {private var currentState: OrderState OrderState.WAIT_PAYMENT//事件买家付款fun payment() {}//事件商家发货fun shipment() {}//事件买家收货fun receipt() {}//事件买家/商家取消fun cancelled() {}
}下面是测试代码共测试了三个流程其中流程一、二状态是正常的状态流转流程三在取消状态后再调用发货事件用于检查程序是否响应这一错误事件。
fun main() {println(流程一)val stateMachine1 OrderStateMachine()stateMachine1.payment()stateMachine1.shipment()stateMachine1.receipt()println()println(流程二)val stateMachine2 OrderStateMachine()stateMachine2.cancelled()println()println(流程三)val stateMachine3 OrderStateMachine()stateMachine3.payment()stateMachine3.cancelled()stateMachine3.shipment()println()
}准备工作都做好了下面开始用三种方法进行状态机的实现。
1.状态机实现—分支判断法
这种方式会将需求简单的直译成代码集中处理事件逻辑并在每个事件中考虑所有状态的实现下面按照这种方式将代码补全
//状态机
class OrderStateMachine {private var currentState: OrderState OrderState.WAIT_PAYMENT //已待付款作为初始状态//事件买家付款fun payment() {println(事件买家付款)when (currentState) {OrderState.WAIT_PAYMENT - {ActionGroup.moneyToPlatform()currentState OrderState.WAIT_SHIPMENT}OrderState.WAIT_SHIPMENT, OrderState.WAIT_RECEIPT, OrderState.COMPLETED, OrderState.CANCELLED - {println(待发货、待收货、已完成和已取消的订单不用付款...)}}println(订单状态: ${currentState.desc})}//事件商家发货fun shipment() {println(事件商家发货)when (currentState) {OrderState.WAIT_SHIPMENT - {currentState OrderState.WAIT_RECEIPT}OrderState.WAIT_PAYMENT, OrderState.WAIT_RECEIPT, OrderState.COMPLETED, OrderState.CANCELLED - {println(待付款、待收货、已完成和已取消的订单不用发货...)}}printState()}//事件买家收货fun receipt() {println(事件买家收货)when (currentState) {OrderState.WAIT_RECEIPT - {ActionGroup.moneyToSeller()currentState OrderState.COMPLETED}OrderState.WAIT_PAYMENT, OrderState.WAIT_SHIPMENT, OrderState.COMPLETED, OrderState.CANCELLED - {println(待付款待发货、已完成和已取消的订单不用收货...)}}printState()}//事件买家/商家取消fun cancelled() {println(事件买家/商家取消)when (currentState) {OrderState.WAIT_PAYMENT - {currentState OrderState.CANCELLED}OrderState.WAIT_SHIPMENT - {ActionGroup.moneyToBuyer()currentState OrderState.CANCELLED}OrderState.WAIT_RECEIPT, OrderState.COMPLETED, OrderState.CANCELLED - {println(待收货、已完成和已取消的订单不能取消...)}}printState()}fun printState() {println(订单状态: ${currentState.desc})}}运行一下看结果
流程一
事件买家付款
动作付款给平台...
订单状态: 待发货
事件商家发货
订单状态: 待收货
事件买家收货
动作金额打给商家...
订单状态: 已完成流程二
事件买家/商家取消
订单状态: 已取消流程三
事件买家付款
动作付款给平台...
订单状态: 待发货
事件买家/商家取消
动作金额退还给买家...
订单状态: 已取消
事件商家发货
待付款、待收货、已完成和已取消的订单不用发货...
订单状态: 已取消可以看到流程一二状态正常流转流程三已取消订单并不会响应发货事件代码执行结果是符合预期的。
再看上述代码包含了大量的 if-else / switch-case 判断 when 是 Kotlin语言表达 switch-case 的语法糖这些冗长的分支逻辑很容易改错代码引发Bug可读性很差。如果再增加 待评价 状态和 评价 事件那么这时代码的改动会涉及到所有事件方法代码维护性也很差。
这种实现方法只适合简单的状态机对于复杂的状态机还是用下面两种实现方式。
2.状态机实现—查表法
我们把事件也抽象成枚举
//订单事件
enum class OrderEvent(val value: Int, val desc: String) {PAYMENT(0, 事件买家付款),SHIPMENT(1, 事件商家发货),RECEIPT(2, 事件买家收货),CANCEL(3, 事件买家/商家取消)
}再根据上面的状态流转图定义出状态的流转表
状态\事件PAYMENTSHIPMENTRECEIPTCANCELWAIT_PAYMENTWAIT_SHIPMENT动作moneyToPlatform\\CANCELLEDWAIT_SHIPMENT\WAIT_RECEIPT\CANCELLED动作moneyToBuyerWAIT_RECEIPT\\COMPLETED动作moneyToSeller\COMPLETED\\\\CANCELLED\\\\
这个表也是查表法的核心只要能在代码中正确的表达这个表就可以非常简单的实现状态机这里用了一个取巧的方式将状态枚举和事件枚举的 value 和所在数组下标进行了对应代码如下
//状态机
class OrderStateMachine2 {//状态-事件流转表private val STATE_EVENT_TABLE arrayOf(arrayOfOrderState?(OrderState.WAIT_SHIPMENT, null, null, OrderState.CANCELLED),arrayOfOrderState?(null, OrderState.WAIT_RECEIPT, null, OrderState.CANCELLED),arrayOfOrderState?(null, null, OrderState.COMPLETED, null),arrayOfOrderState?(null, null, null, null),arrayOfOrderState?(null, null, null, null))//状态-动作触发表private val STATE_ACTION_TABLE arrayOf(arrayOfFunction0Unit?(::moneyToPlatform, null, null, null),arrayOfFunction0Unit?(null, null, null, ::moneyToBuyer),arrayOfFunction0Unit?(null, null, ::moneyToSeller, null),arrayOfFunction0Unit?(null, null, null, null),arrayOfFunction0Unit?(null, null, null, null))private var currentState: OrderState OrderState.WAIT_PAYMENT //已待付款作为初始状态//事件买家付款fun payment() {println(事件买家付款)executeEvent(OrderEvent.PAYMENT)println(订单状态: ${currentState.desc})}//事件商家发货fun shipment() {println(事件商家发货)executeEvent(OrderEvent.SHIPMENT)printState()}//事件买家收货fun receipt() {println(事件买家收货)executeEvent(OrderEvent.RECEIPT)printState()}//事件买家/商家取消fun cancel() {println(事件买家/商家取消)executeEvent(OrderEvent.CANCEL)printState()}private fun executeEvent(event: OrderEvent) {//触发动作STATE_ACTION_TABLE.getOrNull(currentState.value)?.getOrNull(event.value)?.invoke()val nextState STATE_EVENT_TABLE.getOrNull(currentState.value)?.getOrNull(event.value)if (nextState ! null) {currentState nextState} else {println(${currentState.desc}不响应${event.desc})}}fun printState() {println(订单状态: ${currentState.desc})}
}执行结果
流程一
事件买家付款
动作付款给平台...
订单状态: 待发货
事件商家发货
订单状态: 待收货
事件买家收货
动作金额打给商家...
订单状态: 已完成流程二
事件买家/商家取消
订单状态: 已取消流程三
事件买家付款
动作付款给平台...
订单状态: 待发货
事件买家/商家取消
动作金额退还给买家...
订单状态: 已取消
事件商家发货
已取消不响应事件商家发货
订单状态: 已取消这种方式的核心是维护好两个表如果新增状态和事件那么只需要关注表关系是否正确即可甚至无需对其他代码进行改动比较适合状态比较多的场景。缺点是对于动作触发不是很灵活由于每个动作触发的参数传递可能不一样状态和动作甚至有依赖关系这种场景下查表法就非常不灵活了。
3.状态机实现—状态模式
查表法对于复杂动作场景有一定局限性分支判断法的代码可读性和可维护性比较差接下来就是主角-状态模式出场了。
状态模式其实就是对分支判断法的进一步封装通过将事件触发导致的状态转移和动作执行拆分到不同的状态类中从而避免大量分支判断逻辑提高代码可读性和可扩展性这就是状态模式。
首先定义出事件接口
//状态流转事件接口各状态需实现
interface IOrder {fun getDesc(): String//Kotlin中接口支持默认实现高版本的Java也支持了fun payment(stateMachine: OrderStateMachine3): Unit {println(${stateMachine.getOrderState().getDesc()}不响应事件买家付款)}fun shipment(stateMachine: OrderStateMachine3): Unit {println(${stateMachine.getOrderState().getDesc()}不响应事件商家发货)}fun receipt(stateMachine: OrderStateMachine3): Unit {println(${stateMachine.getOrderState().getDesc()}不响应事件买家收货)}fun cancel(stateMachine: OrderStateMachine3): Unit {println(${stateMachine.getOrderState().getDesc()}不响应事件买家/商家取消)}
}定义所有状态类并实现总的事件接口然后根据具体状态选择实现抽象的事件方法并在方法中实现状态流转和动作的触发逻辑。比如待付款状态订单只关心付款事件和取消事件那么只实现这两个方法即可
//object是Kotlin的单例写法,JVM 加载类时就创建了单例对象
//状态待付款
object OrderWaitPayment : IOrder {override fun getDesc(): String 待付款override fun payment(stateMachine: OrderStateMachine3) {stateMachine.setOrderState(OrderWaitShipment)println(动作付款给平台...)}override fun cancel(stateMachine: OrderStateMachine3) {stateMachine.setOrderState(OrderCanceled)}
}//状态待发货
object OrderWaitShipment : IOrder {override fun getDesc(): String 待发货override fun shipment(stateMachine: OrderStateMachine3) {stateMachine.setOrderState(OrderWaitReceipt)}override fun cancel(stateMachine: OrderStateMachine3) {stateMachine.setOrderState(OrderCanceled)println(动作金额退还给买家...)}}//状态待收货
object OrderWaitReceipt : IOrder {override fun getDesc(): String 待收货override fun receipt(stateMachine: OrderStateMachine3) {stateMachine.setOrderState(OrderCompleted)println(动作金额打给商家...)}
}//状态已完成
object OrderCompleted: IOrder {override fun getDesc(): String 已完成
}//状态已取消
object OrderCanceled: IOrder {override fun getDesc(): String 已取消
}状态机代码
//状态机
class OrderStateMachine3 {private var currentState: IOrder OrderWaitPayment //待付款作为初始状态fun setOrderState(orderState: IOrder) {currentState orderState}fun getOrderState(): IOrder currentState//事件买家付款fun payment() {println(事件买家付款)currentState.payment(this)printState()}//事件商家发货fun shipment() {println(事件商家发货)currentState.shipment(this)printState()}//事件买家收货fun receipt() {println(事件买家收货)currentState.receipt(this)printState()}//事件买家/商家取消fun cancel() {println(事件买家/商家取消)currentState.cancel(this)printState()}private fun printState() {println(订单状态: ${currentState.getDesc()})}
}执行结果
流程一
事件买家付款
动作付款给平台...
订单状态: 待发货
事件商家发货
订单状态: 待收货
事件买家收货
动作金额打给商家...
订单状态: 已完成流程二
事件买家/商家取消
订单状态: 已取消流程三
事件买家付款
动作付款给平台...
订单状态: 待发货
事件买家/商家取消
动作金额退还给买家...
订单状态: 已取消
事件商家发货
已取消不响应事件商家发货
订单状态: 已取消代码输出符合预期如果增加新的状态和事件那么只需要新增个状态类和方法即可扩展非常方便可读性也很高。
缺点是如果状态非常多也需要定义出大量的状态类如果状态类的实现又只涉及状态流转而少有事件执行那么类的模板代码甚至超过具体逻辑代码就得不偿失了这种情况图表法更适用。
总结
状态机三要素状态State、事件Event、动作Action事件驱动状态的流转并触发动作的执行。
实现状态及三种方式 分支判断法 优点实现简单适合状态较少的简单场景。 缺点大量 if-else 或 switch-case 代码可读性和可扩展性差不适合复杂逻辑。 查表法 优点代码中只需维护好状态流转表即可代码比较直观适合状态较多且增加频繁的场景。 缺点不适合动作执行复杂的场景如订单系统 状态模式 优点分支判断法的进一步封装加强了代码可读性和扩展性适合状态数量适中动作执行复杂的场景。 缺点大量状态会导致状态类繁多体积变大。
如何选择状态机的实现方法还需要根据具体场景考虑当前需求实现健壮性保持一定前瞻性编码初期避免过度封装适时重构保持良好编码习惯。