首先简述一个场景,订单的状态流转。 一个订单会有很多种状态:临时单、已下单、待支付、已支付、已完成、退款中等等。每一种状态都和其扭转前的状态、在扭转前状态所执行的操作有关。
一 引子
举例一个过程:用户将商品加入购物车,在后台生成了一个所谓的“临时单”,这个订单实际上还没有正式生成,因为用户仍然没有点击下单。只有当用户下单后,这个“临时单”才可以转化为一个“待支付的订单”。那么这个过程中:只有将一个处于“临时单”状态的订单执行下单操作,才能得到一个状态为“待支付”的订单。 即--一个前置状态+一个恰当的操作,才能扭转订单的状态。在这个过程中,如果是硬编码,那么我们需要一系列的 if...else 语句来检查订单的当前状态、可执行操作以及这两个的组合得到的下一个应该被流转的状态值。如果订单的状态流转很复杂的话,写出来的逻辑就会很复杂,并且可读性很低。后期的维护就是一个坑。
二 状态设计模式与订单状态流转
处理这个问题,我们可以使用 状态机设计模式 来处理。对应到实践,就是状态机。
关于状态机设计模式的具体内容,可以自行百度。这里用简单的一句话来概括的话:对象的内部状态随外部执行条件的变化而变化。再映射到订单状态的流转上:订单的状态,随订单当前状态和目前执行操作的组合而变化。
三 编码前的抽象与设计
图示模拟一个订单状态的流转流程。从一个临时订单开始,每当订单处于某一个已知的状态的时候,要想让这个订单改变状态,就需要我们去执行对应的操作。
从状态机角度来说,我们先将各种信息进行抽象和处理
3.1 代码抽象
编写对应订单状态枚举类
public enum OrderStatusEnum {
CREATE_EVENT(1, "创建订单"),
FORMAL_EVENT(2, "正式订单"),
NEED_PAY(3, "待支付"),
PAY_DONE(4, "已支付"),
ORDER_FINISHED(5, "订单已完成"),
ORDER_CANCEL(6, "订单已取消");
OrderStatusEnum(int status, String desc) {
this.status = status;
this.desc = desc;
}
public int status;
public String desc;
}
枚举类中先准备好需要用的状态信息。
先用一张图来描述整个工作机制:
然后是需要的核心代码部分:一个管理订单状态的中转站manager类,一组用于扭转订单状态的operator类,一组扭转完订单状态后执行后续逻辑操作的processor类。
manager类需要根据对应传入的当前订单状态、要对该订单执行操作来得到这个订单的结果状态(依靠对应的opertor类),然后执行一系列需要的业务逻辑操作(编写对应的processor类)。这样的好处就是将订单状态流转和对应的业务处理解耦。并且也不会再有一堆繁杂的 if...else 操作。每当需要新的订单状态流转操作的时候,可以去编写对应的一套operator和processor组件来完成,和已有业务的分离度很高。
接下来贴代码举例
/**
* 订单状态流转管理器--状态机核心组件
* @author [email protected]
* @date 2018/10/29 19:21
**/
@Component
public class OrderStateManager {
Map orderOperatorMaps = new HashMap();
Map orderProcessorMaps = new HashMap();
public OrderStateManager() { }
/**
* 状态流转方法
* @param orderId 订单id
* @param event 流转的订单操作事件
* @param status 当前订单状态
* @return 扭转后的订单状态
*/
public int handleEvent(final String orderId, OrderStatusEnum event, final int status) {
if (this.isFinalStatus(status)) {
throw new IllegalArgumentException("handle event can't process final state order.");
}
// 获取对应处理器,根据入参状态和时间获取订单流转的结果状态
AbstractOrderOperator abstractOrderOperator = this.getStateOperator(event);
int resState = abstractOrderOperator.handleEvent(status, event);
// 得到结果状态,在对应的processor中处理订单数据及其相关信息
AbstractOrderProcessor orderProcessor = this.getOrderProcessor(event);
if (!orderProcessor.process(orderId, resState)) {
throw new IllegalStateException(String.format("订单状态流转失败,订单id:%s", orderId));
}
return resState;
}
/**
* 根据入参状态枚举实例获取对应的状态处理器
* @param event event
* @return
*/
private AbstractOrderOperator getStateOperator(OrderStatusEnum event) {
AbstractOrderOperator operator = null;
for (Map.Entry entry: orderOperatorMaps.entrySet()) {
if (event.status == entry.getKey()) {
operator = entry.getValue();
}
}
if (null == operator) {
throw new IllegalArgumentException(String.format("can't find proper operator. The parameter state :%s", event.toString()));
}
return operator;
}
/**
* 根据入参状态枚举实例获取对应的状态后处理器
* @param event event
* @return
*/
private AbstractOrderProcessor getOrderProcessor(OrderStatusEnum event) {
AbstractOrderProcessor processor = null;
for (Map.Entry entry : orderProcessorMaps.entrySet()) {
if (event.status == entry.getKey()) {
processor = entry.getValue();
}
}
if (null == processor) {
throw new IllegalArgumentException(String.format("can't find proper processor. The parameter state :%s", event.toString()));
}
return processor;
}
/**
* 判断是不是已完成订单
* @param status 订单状态码
* @return
*/
private boolean isFinalStatus(int status) {
return OrderStatusEnum.ORDER_FINISHED.status == status;
}
}
核心的代码就是类中的 handleEvent 方法。
对应的获取到的组件处理类示例:
/**
* @author [email protected]
* @date 2018/10/29 17:47
**/
@Data
public abstract class AbstractOrderOperator {
int status;
public abstract int handleEvent(int orderStatus, OrderStatusEnum orderStatusEnum);
}
===================================
/**
* 创建订单操作状态流转
* @author [email protected]
* @date 2018/10/29 17:50
**/
@Component
@OrderOperator
public class CreateOrderOperator extends AbstractOrderOperator {
public CreateOrderOperator() {
super.setStatus(1);
}
@Override
public int handleEvent(int orderStatus, OrderStatusEnum orderStatusEnum) {
if (orderStatus != OrderStatusEnum.CREATE_EVENT.status && orderStatus != OrderStatusEnum.ORDER_CANCEL.status) {
throw new IllegalArgumentException(String.format("create operation can't handle the status: %s", orderStatus));
}
System.out.println("进入创建订单状态扭转处理器...");
switch (orderStatusEnum) {
case CREATE_EVENT:
return OrderStatusEnum.FORMAL_EVENT.status;
case ORDER_CANCEL:
return OrderStatusEnum.ORDER_CANCEL.status;
default:
return getStatus();
}
}
}
后处理器
/**
* 订单处理器
* @author [email protected]
* @date 2018/10/31 14:57
**/
@Data
public abstract class AbstractOrderProcessor {
int status;
public abstract boolean process(String orderId, Object... params);
}
================================
/**
* @author [email protected]
* @date 2018/10/31 14:59
**/
@Component
@OrderProcessor
public class CreateOrderProcessor extends AbstractOrderProcessor{
public CreateOrderProcessor() {
super.setStatus(1);
}
@Override
public boolean process(String orderId, Object... params) {
// TODO 创建/取消订单对应的数据库修改,mq发送等操作,可以在此处process方法中完成
System.out.println("进入创建订单后处理器...");
return true;
}
}
这些组件类都是依赖于spring的组件扫描注入。如果要定制化地处理自己的组件类。可以用一些其他的技巧来处理。比如此处使用到了自定义注解,通过自定义注解+自定义状态机初始化类来完成对应组件类的筛选与初始化。将这个初始化类加载完毕,状态机就可以正常使用了。
/**
* 状态机前置激活类,在spring中扫描配置此类
* 使用自定义注解标记对应的状态处理器和后置处理器并在初始化操作中完成对应处理器的初始化。
* @author [email protected]
* @date 2018/10/31 15:30
**/
@Component
public class Initialization implements BeanPostProcessor {
@Resource
OrderStateManager manager;
@Nullable
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof AbstractOrderOperator && bean.getClass().isAnnotationPresent(OrderOperator.class) ) {
AbstractOrderOperator orderState = (AbstractOrderOperator) bean;
manager.orderOperatorMaps.put(orderState.getStatus(), orderState);
}
if (bean instanceof AbstractOrderProcessor && bean.getClass().isAnnotationPresent(OrderProcessor.class) ) {
AbstractOrderProcessor orderProcessor = (AbstractOrderProcessor) bean;
manager.orderProcessorMaps.put(orderProcessor.getStatus(), orderProcessor);
}
return bean;
}
}
这里有一个问题就是在正式开发环境中,依赖于项目的spring环境,需要在状态机正式运行前将对应的状态扭转组件类(operator和processor)注入到环境中。
四 示例
写完公司的项目后,自己再动手编写了一个简化版的demo。包含了上述代码和测试用例。
订单状态机demo
- 作者:柯基去哪了
链接:https://www.jianshu.com/p/fe292b15a06a
来源:
著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。