状态机(FSM)、状态模式在金融支付系统中的应用(二)

  • Post author:
  • Post category:其他





前言


上一篇文章 “

状态机(FSM)、状态模式在金融支付系统中的应用(一)

” 中粗略的讲了一下状态机和状态模式的概念,但是实际工作中代码怎么写估计很多人还云里雾里,下面会以代码的方式真正展示一下实际工作中状态模式和状态机的使用,写法有很多这里我展示一种比较容易理解和操作的。



1.将UML转化成实际的代码

还是回归状态模式的UML图:

在这里插入图片描述

这里我们可以分析一下关键点:


Context的是一个聚合状态的类,持有所有状态相当于一个状态管理者



State是一个抽象的状态,里面可以定义一些获取状态的方法等,子类可以进行覆盖


找到了关键点接下来我们以一个简化后的退款状态机为例子展开:

在这里插入图片描述

1.先定义一个Context用于维护状态本身,这里我们能看到context本身还可以增加类似于通知mq发email消息等扩充功能。

public abstract class AbstractContext {
    //下个状态
    public abstract void doNext(String result);
    //获取当前状态
    public abstract String status();
    //状态顺序
	public abstract Integer order();
	
	public void notifyEmial() throws Exception {
		//发送email通知
	}
	public void notifyMQ() throws Exception {
		//通知mq
	}
}

2.然后我们建一个退费的Context,他持有所有的状态,对外统一处理管理实现了高内聚

public class RefundContext extends AbstractContext{
	//下面就是不同的状态
	public final RefundState APPLIED = new ApplyState(this);
	public final RefundState PROCESSING = new ProcessState(this);  
    public final RefundState SUCCESS = new SuccessState(this);
    public final RefundState FAILURE = new FailState(this);
    public final RefundState REFUNDING = new RefundingState(this);
	//默认第一状态就是APPLIED
	private RefundState state = APPLIED;
	
	//根据传进来的状态获取Context
	public RefundContext(String fromStatus) throws Exception{
		AbstractState returnState = getState(fromStatus);
		if(returnState !=null) 
			state = (RefundState)returnState;
	}

	/**
	 * 反射根据当前状态枚举获取状态对象
	 *
	 */
	public AbstractState getState(String fromStatus) throws IllegalAccessException {
		Field[] fields = this.getClass().getDeclaredFields();
		for (Field field : fields) {
			field.setAccessible(true);
			if (Modifier.isPublic(field.getModifiers())
					&& Modifier.isFinal(field.getModifiers())) {
				AbstractState state = (AbstractState) field.get(this);
				if (fromStatus.equals(state.status()))
					return state;

			}
		}
		return null;
	}
}	

3.定义一个抽象的状态,可以包含一些状态变更的方法:

public class AbstractState {
    //下个状态
    public abstract void doNext(String result);
    //获取当前状态
    public abstract String status();
    //状态顺序
	public abstract Integer order();
    }

已申请状态—-》下一个状态为

处理中

public class ApplyState extends AbstractState {

	RefundContext context;

	/**
	 * @param context
	 */
	public ApplyState(RefundContext context) {
	this.context=context;
	}
	
	public String status(){
		return PropertyEnum.STATUS_APPLY.getCode();
	}
	
	@Override
	public Integer order(){
		return 1;
	}
	//如果成功下一个状态是处理中
	@Override
	public void doNext(String resultStatus) {
		if(PropertyEnum.STATUS_SUCCESS.getCode().equals(resultStatus)){
        	this.context.setState(context.PROCESSING);
		}
	}
}

处理中状态—-》下一个状态:如果成功则会变成

退款中

,如果失败则会变更为

失败

public class ProcessState extends RefundState {

	/**
	 * @param context
	 */
	public ProcessState(RefundContext context) {
		super(context);
	}
	
	public String status(){
		return PropertyEnum.STATUS_PROCESS.getCode();
	}
	
	@Override
	public Integer order(){
		return 2;
	}
	
	@Override
	public void doNext(String result) {
		if(PropertyEnum.STATUS_SUCCESS.getCode().equals(result)){
			this.context.setState(context.REFUNDING);
		}else if(PropertyEnum.STATUS_FAIL.getCode().equals(result)){
			this.context.setState(context.FAILURE);
		}else {
			this.context.setState(context.FAILURE);
		}
	}
	
}

还有

退款中



失败

这两个状态就不贴代码了,可以根据前两个状态推导出来。



2.退费场景中如何使用?

当一个退费订单被创建的时候,直接通过Context去获取当前状态:

	//伪代码关注如何创建和使用context
	public void addRefund() {
	//创建一个退费的Contentxt
        //这里由于是初始化所以是APPLIED申请状态
        RefundContext refundContext = new RefundContext();
        //APPLIED---》PROCESSING
		String nextStatus = context.doNext(PropertyEnum.STATUS_SUCCESS.getCode());
		RefundDO renfundDO = new RefundDO();
		renfundDO.setStatus(nextStatus);
		//插入退费订单
		insert(refundDO);
		doRefund(refundContext,refundDO)
	}

创建完退费订单后去请求外部接口进行退费

	//伪代码关注如何创建和使用context
	public void doRefund(RefundContext context,RefundDO renfundDO) {
	//先取到当前状态
		renfundDO.setFromStatus(context.status())
	//请求退费成功
		if(requestRefund()){
			//成功后的状态流转 PROCESSING---》REFUNDING
			String nextStatus = context.doNext(PropertyEnum.STATUS_SUCCESS.getCode());
			renfundDO.setStatus(nextStatus);
		}else{
			//失败后的状态流转 PROCESSING -----》FAIL
			String nextStatus = context.doNext(PropertyEnum.STATUS_FAIL.getCode());
			String status = context.status();
			renfundDO.setStatus(status);
		}
	   //更新退费订单
		update(refundDO);
		
	}

从上面我们可以看到,状态机的使用者不用关注

状态流转本身,只需要把响应当前状态传入

既可以获得后面的状态,这也是“


状态机


”的由来因为,本身看起来像一个机器在运行。

如果对上面为什么要使用

fromStatus

来记录原来状态的同学可以看看我的还有一篇文章

如何保证异步重复消息状态更新正确性 —-(代码级别)



3.总结

这篇文章我们根据UML一步步的实现了一个状态机,以及演示了如何在真实场景中使用它,当然真实的支付系统中状态机还有另一个大的用处就是“

事件

”这个在文章(一)中有提到,一般如果到达一种终态如:成功或者失败,都会异步发送MQ消息,这里可以利用状态机的接口直接进行操作,由于篇幅原因这一部分就不详尽展示了,不过在接口中已经有体现,希望本篇文章对你有用。



版权声明:本文为boboaisisi原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。