如何做好多狀態的流程控制

初次寫流程控制的小夥伴肯都有體會,事務處理的流程中狀態一旦多了,就會出現非常多的if-elseif 語句,對於出現Bug解決問題來說,不得不對整段代碼的邏輯理一遍,然後才能分析出到底是哪裏出了問題。那麼,有什麼別的好的解決方案嗎?

在設計模式中,其實早就已經爲我們考慮到了這一個場景,我們可以利用狀態模式基本還原這樣的任務。

在這裏插入圖片描述

整個實現上的核心基礎類就是 基礎狀態抽象類 和基礎有限自動機。業務狀態標識了業務流程中的一個個節點,而有限狀態機是業務流程的運輸體。想象一下,把汽車生產流程當做一個辦事流程,那麼有限自動機就是傳送帶,狀態就是一道道工序。

// State.java
/**
 * @auther chaojilaji
 * @version 1.0
 * <pre>狀態基礎</pre>
 * <p>主要定義了基礎的狀態的進入,狀態的釋放等功能</p>
 */
public abstract class State {
    public State() {
    }
    /**
     * 將當前狀態加入到某個狀態機中
     * @param var1 有限狀態機
     */
    public abstract void enter(FiniteStateMachine var1);
    /**
     * 從某個狀態機中退出
     * @param var1 有限狀態機
     */
    public abstract void exit(FiniteStateMachine var1);
    public abstract void pause(FiniteStateMachine var1);
    public abstract void resume(FiniteStateMachine var1);
}

基礎狀態類只需要定義出所有狀態的基礎功能即可。即 進入傳送帶、從傳送帶上面取出來等。
下面來定義基礎傳動帶

// FiniteStateMachine.java
/**
 * @author chaojilaji
 * @version 1.0
 * <pre>有限狀態機可以看做是處理事務流程的載體</pre>
 */
public class FiniteStateMachine{


    protected EmptyState nullState = new EmptyState();
    private Stack<State> states = new Stack();

    /**
     *
     * @return 獲取當前狀態機中的狀態
     */
    public State getCurrentState() {
        return (State)(this.states.size() == 0 ? this.nullState : (State)this.states.peek());
    }

    public FiniteStateMachine() {
    }

    /**
     * 狀態切換
     * @param newSate 進入到新狀態
     */
    public void ChangeTo(State newSate) {
        this.getCurrentState().exit(this);
        if (this.states.size() > 0) {
            this.states.pop();
        }

        this.states.push(newSate);
        System.out.println("狀態----------"+newSate.getClass());
        this.getCurrentState().enter(this);
    }
}

// EmptyState.java
public class EmptyState extends State{

    public EmptyState() {
    }
    @Override
    public void enter(FiniteStateMachine var1) {

    }

    @Override
    public void exit(FiniteStateMachine var1) {

    }

    @Override
    public void pause(FiniteStateMachine var1) {

    }

    @Override
    public void resume(FiniteStateMachine var1) {

    }
}

好了,現在第一層基礎模塊已經寫好了,現在我們來實現一個流程demo,如何完成一個處理任務的流程。
在這裏插入圖片描述

// 業務流程中的傳送帶
/**
 * @author chaojilaji
 * @version 1.0
 * <pre>掃描任務領域有限狀態機</pre>
 */
public class TaskFiledMachine extends FiniteStateMachine implements TaskThingListener{

    private Long taskId;


    public TaskFiledMachine(Long taskId){
        this.taskId = taskId;
        nullState = new EmptyState();
        ChangeTo(new NotBeginState());
    }


    @Override
    public void onCommand(OperateCode operateCode) {
        try {
            ((TaskState)getCurrentState()).onCommand(operateCode);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

// TaskThingListener 接口定義了傳送帶在這個業務領域中要乾的事兒
public interface TaskThingListener {

    void onCommand(OperateCode operateCode);

}

// OperateCode 是操作碼
public class OperateCode{

    private Integer code;
    private Map<String,Object> body;

    public OperateCode(Integer code){
        this.code = code;
    }

    public void setBody(Map<String, Object> body) {
        this.body = body;
    }

    public Integer getCode() {
        return code;
    }

    public Map<String, Object> getBody() {
        return body;
    }
}

傳送帶建好了,現在我們需要定義該業務流程下的狀態基類
在這裏插入圖片描述

public abstract class TaskState extends State {

    /**
     * 指定當前業務狀態所屬的業務狀態機
     */
    protected TaskFiledMachine taskFiledMachine;


    public abstract void onCommand(OperateCode operateCode);


    @Override
    public void enter(FiniteStateMachine var1) {
        taskFiledMachine = (TaskFiledMachine) var1;
    }

    @Override
    public void exit(FiniteStateMachine var1) {
        taskFiledMachine = null;
    }

    @Override
    public void pause(FiniteStateMachine var1) {

    }

    @Override
    public void resume(FiniteStateMachine var1) {

    }
}

然後我們只需要基於這個業務場景,通過繼承這個TaskState ,定義出一道道工序(狀態)

// 未開始的狀態
/**
 * @author chaojilaji
 * @version 1.0
 * <pre>未開始的狀態</pre>
 */
@Slf4j
public class NotBeginState extends TaskState {

    @Override
    public void onCommand(OperateCode operateCode) {
        if (operateCode.getCode().equals(OperateCodeEnum.NEW_TASK.getCode())){
            log.info("新建任務了");
            taskFiledMachine.ChangeTo(new QueueState());
        }
    }
}
// 隊列中狀態
/**
 * @author chaojilaji
 * @version 1.0
 * <pre>隊列狀態</pre>
 */
@Slf4j
public class QueueState extends TaskState {

    @Override
    public void onCommand(OperateCode operateCode){
        if (operateCode.getCode().equals(OperateCodeEnum.INIT_STATE.getCode())){
            log.info("開始做---出隊列---後要做的事兒,先檢測參數完整性,再做一些處理");
            if (operateCode.getBody().containsKey("task_id")){
                log.info("taskId {},進入到隊列中處理",operateCode.getBody().get("task_id"));
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("taskId {},隊列中處理完畢,轉入到掃描中狀態",operateCode.getBody().get("taskId"));
            taskFiledMachine.ChangeTo(new ScanningState());
            taskFiledMachine.onCommand(operateCode);
        }
    }
}

// 任務執行中狀態
/**
 * @author chaojilaji
 * @version 1.0
 * <pre>掃描中</pre>
 */
@Slf4j
public class ScanningState extends TaskState {

    @Override
    public void onCommand(OperateCode operateCode) {
        if (operateCode.getCode().equals(OperateCodeEnum.INIT_STATE.getCode())){
            log.info("開始做---掃描中---要做的事兒,先檢測參數完整性,再做一些處理");
            if (operateCode.getBody().containsKey("task_id")){
                log.info("taskId {},進入到掃描中處理",operateCode.getBody().get("task_id"));
            }
            try {
                Thread.sleep(1000*1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("taskId {},掃描中處理完畢,轉入到掃描完畢狀態",operateCode.getBody().get("taskId"));
            taskFiledMachine.ChangeTo(new ScanEndState());
            taskFiledMachine.onCommand(operateCode);
        }
    }
}

// 任務執行完畢狀態
/**
 * @author chaojilaji
 * @version 1.0
 * <pre>掃描完成狀態</pre>
 */
@Slf4j
public class ScanEndState extends TaskState {

    @Override
    public void onCommand(OperateCode operateCode) {
        if (operateCode.getCode().equals(OperateCodeEnum.INIT_STATE.getCode())){
            log.info("開始做---掃描完畢---要做的事兒,先檢測參數完整性,再做一些處理");
            if (operateCode.getBody().containsKey("task_id")){
                log.info("taskId {},進入到掃描完畢處理",operateCode.getBody().get("task_id"));
            }
            try {
                Thread.sleep(1000*1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("taskId {},掃描完畢處理完畢,轉入到掃描完畢狀態",operateCode.getBody().get("taskId"));
        }
    }
}

最後來看看如何觸發這個流程

// 定義一個創建任務的接口,初始化流程
@Autowired
private ScanTask scanTask;

@PostMapping("/change/state")
    @ResponseBody
    public Object changeStatus(@RequestParam("task_id")final Long taskId,
                               @RequestParam("operate_code")final Integer operateCode){
        Map<String,Object> ans = new HashMap<>();
        ans.put("code",400);
        if (Objects.isNull(taskId)){
            return ans;
        }
        TaskFiledMachine taskFiledMachine = null;
        if (scanTask.taskFiledMachineConcurrentHashMap.containsKey(taskId)){
           taskFiledMachine = scanTask.taskFiledMachineConcurrentHashMap.get(taskId);
           if (Objects.isNull(taskFiledMachine)){
               return ans;
           }

        }else {
            taskFiledMachine = new TaskFiledMachine(taskId);
        }
        try {
            taskFiledMachine.onCommand(new OperateCode(operateCode));
            scanTask.taskFiledMachineConcurrentHashMap.put(taskId,taskFiledMachine);
            ans.put("code",200);
        }catch (Exception e){
            e.printStackTrace();
        }
        return ans;
    }
    
// 流程化任務服務
@Service
public class ScanTask implements InitializingBean {

    private PriorityBlockingQueue<Long> tasks = new PriorityBlockingQueue<>(16);
    public Map<Long, TaskFiledMachine> taskFiledMachineConcurrentHashMap = new ConcurrentHashMap<>();

    private void dispatch() throws InterruptedException {
        while (true){
            Long taskId = tasks.take();
            TaskFiledMachine taskFiledMachine = taskFiledMachineConcurrentHashMap.get(taskId);
            taskFiledMachine.onCommand(new OperateCode(OperateCodeEnum.INIT_STATE.getCode()));
        }
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (true){

                    }
                }catch (Exception e){
                    e.printStackTrace();
                }

            }
        }).start();
    }
}

簡單來說,通過onCommand的調用進入到狀態處理,狀態間通過changeTo不斷的轉狀態。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章