订单及其状态机的设计实现
状态机简介:
状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。【规则的抽象】
有限状态机一般都有以下特点:
(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;
}
}
}


