订单及其状态机的设计实现
状态机简介:
状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。【规则的抽象】
有限状态机一般都有以下特点:
(1)可以用状态来描述事物,并且任一时刻,事物总是处于一种状态;
(2)事物拥有的状态总数是有限的;
(3)通过触发事物的某些行为,可以导致事物从一种状态过渡到另一种状态;
(4)事物状态变化是有规则的,A状态可以变换到B,B可以变换到C,A却不一定能变换到C;
(5)同一种行为,可以将事物从多种状态变成同种状态,但是不能从同种状态变成多种状态。
状态机这种描述客观世界的方式就是将事物抽象成若干状态,然后所有的事件和规则导致事物在这些状态中游走。最终使得事物“自圆其说”。
很多通信协议的开发都必须用到状态机;一个健壮的状态机可以让你的程序,不论发生何种突发事件都不会突然进入一个不可预知的程序分支。
- 状态机示例:
四大概念:
状态(state) |
一个状态机至少要包含两个状态。 分为:现态(源状态)、次态(目标状态) 状态可以理解为一种结果,一种稳态形式,没有扰动会保持不变的。
状态命名形式: 1.副词+动词;例如:待审批、待支付、待收货 这种命名方式体现了:状态机就是事件触发状态不断迁徙的本质。表达一种待触发的感觉。 2.动词+结果;例如:审批完成、支付完成 3.已+动词形式;例如:已发货、已付款 以上两种命名方式体现了:状态是一种结果或者稳态的本质。表达了一种已完成的感觉。
角色很多的时候,为了表示清晰,可以加上角色名:例如:待财务审批、主管已批准 命名考虑从用户好理解角度出发。
|
事件(event) or 触发条件 |
又称为“条件”,就是某个操作动作的触发条件或者口令。当一个条件满足时,就会触发一个动作,或者执行一次状态迁徙 这个事件可以是外部调用、监听到消息、或者各种定时到期等触发的事件。 对于灯泡,“打开开关”就是一个事件。 条件命名形式:动词+结果;例如:支付成功、下单时间>5分钟 |
动作(action) |
事件发生以后要执行动作。例如:事件=“打开开关指令”,动作=“开灯”。一般就对应一个函数。 条件满足后执行动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。 动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。 那么如何区分“动作”和“状态”? “动作”是不稳定的,即使没有条件的触发,“动作”一旦执行完毕就结束了; 而“状态”是相对稳定的,如果没有外部条件的触发,一个状态会一直持续下去。 |
变换(transition) |
即从一个状态变化到另外一个状态 例如:“开灯过程”就是一个变化 |
状态机其他表达方式:
状态机的设计:
信息系统中有很多状态机,例如:业务订单的状态。
状态机的设计存在的问题:什么是状态?到底有多少个状态?要细分到什么程度?
信息系统是现实世界一种抽象和描述。而业务领域中那些已经发生的事件就是事实,信息系统就是将这些事实以信息的形式存储到数据库中,即:信息就是一组事实
信息系统就是存储这些事实,对这些事实进行管理与追踪,进而起到提供提高工作效率的作用。
信息系统就是记录已经发生的事实,信息系统中的状态机基本和事实匹配。即:标识某个事实的完成度。
业务系统,根据实际业务,具体会有哪些发生的事实需要记录,基本这些事实就至少对应一个状态。需要记录的事实就是一种稳态,一种结果。
例如:【待支付】->【已支付】->【已收货】->【已评价】
这些都是系统需要记录的已发生的客观事实。而这些事实就对应了状态,而发生这些事实的事件就对应了触发状态机的转换的事件。
根据自己的业务实际进行分析,并画出状态图即可。
状态机实现方式:状态模式
下面使经典的自动贩卖机例子来说明状态模式的用法,状态图如下:
分析一个这个状态图:
a、包含4个状态(我们使用4个int型常量来表示)
b、包含3个暴露在外的方法(投币、退币、转动手柄、(发货动作是内部方法,售卖机未对外提供方法,售卖机自动调用))
c、我们需要处理每个状态下,用户都可以触发这三个动作。
我们可以做没有意义的事情,在【未投币】状态,试着退币,或者同时投币两枚,此时机器会提示我们不能这么做。
实现逻辑:
任何一个可能的动作,我们都要检查,看看我们所处的状态和动作是否合适。
/** * 自动售货机 * * */ public class VendingMachine { /** * 已投币 */ private final static int HAS_MONEY = 0; /** * 未投币 */ private final static int NO_MONEY = 1; /** * 售出商品 */ private final static int SOLD = 2; /** * 商品售罄 */ private final static int SOLD_OUT = 3; /** * 商品数量 */ private int count = 0; // 当前状态,开机模式是没钱 private int currentStatus = NO_MONEY; // 开机设置商品数量,初始化状态 public VendingMachine(int count) { this.count = count; if (count > 0) { currentStatus = NO_MONEY; } } /** * 投入硬币,任何状态用户都可能投币 */ public void insertMoney() { switch (currentStatus) { case NO_MONEY: currentStatus = HAS_MONEY; System.out.println("成功投入硬币"); break; case HAS_MONEY: System.out.println("已经有硬币,无需投币"); break; case SOLD: System.out.println("请稍等..."); break; case SOLD_OUT: System.out.println("商品已经售罄,请勿投币"); break; } } /** * 退币,任何状态用户都可能退币 */ public void backMoney() { switch (currentStatus) { case NO_MONEY: System.out.println("您未投入硬币"); break; case HAS_MONEY: currentStatus = NO_MONEY; System.out.println("退币成功"); break; case SOLD: System.out.println("您已经买了糖果..."); break; case SOLD_OUT: System.out.println("您未投币..."); break; } } /** * 转动手柄购买,任何状态用户都可能转动手柄 */ public void turnCrank() { switch (currentStatus) { case NO_MONEY: System.out.println("请先投入硬币"); break; case HAS_MONEY: System.out.println("正在出商品...."); currentStatus = SOLD; dispense(); break; case SOLD: System.out.println("连续转动也没用..."); break; case SOLD_OUT: System.out.println("商品已经售罄"); break; } } /** * 发放商品 */ private void dispense() { switch (currentStatus) { case NO_MONEY: case HAS_MONEY: case SOLD_OUT: throw new IllegalStateException("非法的状态..."); case SOLD: count--; System.out.println("发出商品..."); if (count == 0) { System.out.println("商品售罄"); currentStatus = SOLD_OUT; } else { currentStatus = NO_MONEY; } break; } } }