当前存在的问题
在预约下单模块设计订单状态共有7种,如下图:
目前我们使用了待支付、派单中两种状态,在代码中我们发现存在对订单状态进行硬编码的情况:
java
public void paySuccess(TradeStatusMsg tradeStatusMsg) {
...
//订单状态为待支付时当支付成功更新为派单中
if (ObjectUtil.equal(0, orders.getOrdersStatus())) {
...
}
}
随着开发的深入这种代码会越来越多,比如在实现对订单进行关闭时代码会写成如下的形式:
java
1
//运营人员在订单完成时取消订单
//执行此场景下的业务逻辑
//更新订单状态为派单中
update(id,已关闭)
)
if(订单状态==服务中){
//运营人员在服务中时取消订单
//执行此场景下的业务逻辑
//更新订单状态为已关闭
update(id,已关闭)
)
...
以上代码存在问题如下:
-
在业务代码中对订单状态进行硬编码如果有一天更改了业务逻辑就需要更改代码,不方便进行系统扩展和维护。
-
另外对订单状态的管理是散落在很多地方不方便对订单状态进行统一管理和维护。
使用状态机解决问题
什么是状态机?
上图在UML中叫状态图(又叫状态机图),UML是软件开发中的一种建模语言,用来辅助进行软件设计,常用的如:类图、对象、状态图、序列图等,注意状态机图并不是状态机,状态机是一种数学模型,应用在自动化控制、 计算机科学 、通信等很多领域,简单理解状态机就是对状态进行统一管理的数学模型。
我们画的状态图是状态机在 计算机科学 中的应用方法,还有状态机设计模式也是状态机在软件领域的应用方法。
状态机设计模式是状态机在软件中的应用,状态机设计模式描述了一个对象在内部状态发生变化时如何改变其行为,将状态之间的变更定义为事件,将事件暴露出去,通过执行状态变更事件去更改状态,这是状态机设计模式的核心内容。
理解状态机设计模式需要理解四个要素:现态、事件、动作、次态。
(参考地址:baike.baidu.com/item/%E7%8A... )
1、现态:是指当前所处的状态。
2、事件:当一个条件被满足,状态会由现态变为新的状态,事件发生会触发一个动作,或者执行一次状态的迁移。
3、动作:发生事件执行的动作,动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
4、次态:条件满足后要迁往的新状态。
我们拿待支付状态到派单中状态举例:
现态:订单当前处于待支付状态那么现态为待支付。
事件:用户支付成功为事件,支付成功是条件,当条件满足进行状态迁移。
动作:将订单状态由待支付更改为派单中。
次态:派单中。
使用状态机优化代码:
使用状态机之后对代码进行以下优化。
支付成功更改订单状态的代码优化如下:
java
if(支付状态==支付成功){
//调用状态机执行支付成功事件
orderStateMachine.changeStatus(id,支付成功事件);
}
订单取消的代码优化如下:
bash
orderStateMachine.changeStatus(id,订单完成时取消订单事件);
我们发现使用状态机的代码并没有对订单状态进行硬编码,只是指定了订单id和事件名称,执行changeStatus方法后自动更改订单的状态。
实现订单状态机
订单状态枚举类
订单状态枚举类如下:
java
@Getter
@AllArgsConstructor
public enum OrderStatusEnum implements StatusDefine {
NO_PAY(0, "待支付", "NO_PAY"),
DISPATCHING(100, "派单中", "DISPATCHING"),
NO_SERVE(200, "待服务", "NO_SERVE"),
SERVING(300, "服务中", "SERVING"),
FINISHED(500, "已完成", "FINISHED"),
CANCELED(600, "已取消", "CANCELED"),
CLOSED(700, "已关闭", "CLOSED");
private final Integer status;
private final String desc;
private final String code;
/**
* 根据状态值获得对应枚举
*
* @param status 状态
* @return 状态对应枚举
*/
public static OrderStatusEnum codeOf(Integer status) {
for (OrderStatusEnum orderStatusEnum : values()) {
if (orderStatusEnum.status.equals(status)) {
return orderStatusEnum;
}
}
return null;
}
}
java
public interface StatusDefine {
/**
* @return 返回状态编号
*/
Integer getStatus();
/**
* @return 返回状态描述
*/
String getDesc();
/**
* @return 返回状态代码
*/
String getCode();
}
状态变更事件枚举类
所有状态之间存在的变更都需要定义状态变更事件,它实现了StatusChangeEvent 状态变更事件接口,事件对应状态机四要素的事件
代码如下,重点看PAYED:
PAYED(OrderStatusEnum.NO_PAY, OrderStatusEnum.DISPATCHING, "支付成功", "payed")表示由NO_PAY(未支付)状态变化为DISPATCHING(派单中)状态,事件名称为"支付成功"(payed)。
java
@Getter
@AllArgsConstructor
public enum OrderStatusChangeEventEnum implements StatusChangeEvent {
PAYED(OrderStatusEnum.NO_PAY, OrderStatusEnum.DISPATCHING, "支付成功", "payed"),
DISPATCH(OrderStatusEnum.DISPATCHING, OrderStatusEnum.NO_SERVE, "接单/抢单成功", "dispatch"),
START_SERVE(OrderStatusEnum.NO_SERVE, OrderStatusEnum.SERVING, "开始服务", "start_serve"),
COMPLETE_SERVE(OrderStatusEnum.SERVING, OrderStatusEnum.FINISHED, "完成服务", "complete_serve"),
// EVALUATE(OrderStatusEnum.NO_EVALUATION, OrderStatusEnum.FINISHED, "评价完成", "evaluate"),
CANCEL(OrderStatusEnum.NO_PAY, OrderStatusEnum.CANCELED, "取消订单", "cancel"),
SERVE_PROVIDER_CANCEL(OrderStatusEnum.NO_SERVE, OrderStatusEnum.DISPATCHING, "服务人员/机构取消订单", "serve_provider_cancel"),
CLOSE_DISPATCHING_ORDER(OrderStatusEnum.DISPATCHING, OrderStatusEnum.CLOSED, "派单中订单关闭", "close_dispatching_order"),
CLOSE_NO_SERVE_ORDER(OrderStatusEnum.NO_SERVE, OrderStatusEnum.CLOSED, "待服务订单关闭", "close_no_serve_order"),
CLOSE_SERVING_ORDER(OrderStatusEnum.SERVING, OrderStatusEnum.CLOSED, "服务中订单关闭", "close_serving_order"),
// CLOSE_NO_EVALUATION_ORDER(OrderStatusEnum.NO_EVALUATION, OrderStatusEnum.CLOSED, "待评价订单关闭", "close_no_evaluation_order"),
CLOSE_FINISHED_ORDER(OrderStatusEnum.FINISHED, OrderStatusEnum.CLOSED, "已完成订单关闭", "close_finished_order");
/**
* 源状态
*/
private final OrderStatusEnum sourceStatus;
/**
* 目标状态
*/
private final OrderStatusEnum targetStatus;
/**
* 描述
*/
private final String desc;
/**
* 代码
*/
private final String code;
}
定义订单快照类
快照是订单变化瞬间的状态及相关信息。
比如:001号订单创建成功此时记录它的快照信息(订单号、下单人、订单详细信息、订单状态等),当001号订单支付成功由待支付状态变化为派单中状态此时也会记录它的快照信息(订单号、下单人、支付状态、支付相关信息,订单状态等相关信息),由此可以看出订单快照可以追溯订单的历史变化信息,只要状态发生变化便会记录快照。
快照基础类型是StateMachineSnapshot,如果我们要实现订单快照则需要定义一个订单快照类OrderSnapshotDTO 去继承StateMachineSnapshot类型,代码如下:
java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderSnapshotDTO extends StateMachineSnapshot {
//....原来的内容保持不变,添加以下代码
@Override
public String getSnapshotId() {
return String.valueOf(id);
}
@Override
public Integer getSnapshotStatus() {
return ordersStatus;
}
@Override
public void setSnapshotId(String snapshotId) {
this.id = Long.parseLong(snapshotId);
}
@Override
public void setSnapshotStatus(Integer snapshotStatus) {
this.ordersStatus = snapshotStatus;
}
}
java
public abstract class StateMachineSnapshot {
/**
* 返回快照id
* @return
*/
public abstract String getSnapshotId();
/**
* 返回快照状态
* @return
*/
public abstract Integer getSnapshotStatus();
/**
* 设置快照id
*/
public abstract void setSnapshotId(String snapshotId);
/**
* 设置快照状态
*/
public abstract void setSnapshotStatus(Integer snapshotStatus);
}
定义事件变更动作类
当执行状态变更事件会伴随着执行具体的动作,此部分对应状态机四要素中的动作。
定义订单支付成功动作类,实现StatusChangeHandler接口,泛型中指定快照类型。
此动作是订单支付成功执行的动作。
动作类的bean名称为"状态机名称_事件名称",例如下边的动作类bean的名称为order_payed,表示order状态机的payed事件。
java
@Slf4j
@Component("order_payed")
public class OrderPayedHandler implements StatusChangeHandler<OrderSnapshotDTO> {
@Resource
private IOrdersCommonService ordersService;
/**
* 订单支付处理逻辑
*
* @param bizId 业务id
* @param bizSnapshot 快照
*/
@Override
public void handler(String bizId, StatusChangeEvent statusChangeEventEnum, OrderSnapshotDTO bizSnapshot) {
log.info("支付成功事件处理逻辑开始,订单号:{}", bizId);
}
}
定义订单状态机类
AbstractStateMachine状态机抽象类是状态机的核心类,是具体的状态机要继承的抽象类,比如我们实现订单状态机就需要继承AbstractStateMachine抽象类。
成员变量:
初始状态:设置初始状态,比如订单的初始状态为待支付。
状态机名称:返回的状态机的标识,比如订单状态机返回"order"作为订单状态机的名称。
方法:
返回状态机名称:返回状态机的名称
返回初始状态:返回初始状态
启动状态机:开始进行状态机管理,通常在新建实例时调用此方法,比如:新建一个订单调用此方法将订单状态设置为初始状态,传入参数:业务主键(如订单id)
变更状态:调用此方法更改状态
后处理方法:当状态变更后统一执行的逻辑。
下边定义订单状态机类继承此抽象类,代码如下: