【有限状态机(Finite State Machine---FMS)】

本文探讨了有限状态机(FMS)的概念及其在处理复杂业务逻辑中的作用。状态机包括状态、转移、动作和事件四个要素,根据条件触发状态变化。文章介绍了状态表的构成,并通过视频转码过程举例说明。接着,讨论了一个简单的状态机实现,指出了其违反开闭原则的问题,并提出了改进方案,如采用状态模式、设置事件顶层接口和利用反射加注解封装策略。最后提到了自定义实现状态机时可以考虑的功能增强,如状态持久化、上下文传递和并发控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

其实我们在编程时实现相关业务逻辑时经常需要处理各种事件和状态切换,写各种switch/case 和if/else ,所以我们其实可能一直都在跟有限状态机打交道,只是可能没有意识到。在处理一些业务逻辑比较复杂的需求时,可以先看看是否适合用一个有限状态机来描述,如果可以把业务模型抽象成一个有限状态机,那么代码就会逻辑特别清晰,结构特别规整。


下面我们就来聊聊所谓的状态机,以及它如何在代码中实现。
1、状态机的要素    

状态(State):表示对象的某种形态,在当前形态下可能会拥有不同的行为和属性。
       转移(Transition):表示状态变更,并且必须满足确使转移发生的条件来执行。
       动作(Action):表示在给定时刻要进行的活动。
       事件(Event):事件通常会引起状态的变迁,促使状态机从一种状态切换到另一种状态。

                                                                                                                                                                          图一

所有的状态转换都可以概括为:F(S, E) -> (A, S’),即如果当前状态为S,接收到一个事件E,则执行动作A,同时状态转换为下个状态S’。

分类

  • F(S) -> (A, S’) 型状态机:下一状态只由当前状态决定
  • F(S, E) -> (A, S’) 型状态机:下一状态不但与当前状态有关,还与当前输入值有关

2、状态表的要素

现态:是指当前所处的状态。
       条件:又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
       动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
       次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。

我们以视频转码过程(基于业务模型的猜想)用状态表表示整个过程,如下图所示。

现态|次态文件上传转码模板视频转码保存视频
文件上传

N/A

N/A

N/A

N/A

转码模板

触发条件(event):文件上传完成

执行动作(action):添加转码模板

触发条件(event):重新添加转码模板

执行动作(action):添加转码模板

触发条件(event):重新转码,上传转码模板

执行动作(action):添加转码模板

触发条件(event):重新转码,添加模板

执行动作(action):添加转码模板

视频转码

触发条件(event):不需要转码模板

执行动作(action):开始视频转码

触发条件(event):添加转码模板完成

执行动作(action):开始转码

触发条件(event):转码失败或重新转码

执行动作(action):开始转码

触发条件(event):重新转码

执行动作(action):开始转码

保存视频

触发条件(event):不需要转码

执行动作(action):保存视频文件

N/A

触发条件(event):转码完成

执行动作(action):保存视频

N/A

 

2、简单状态机实现

代码结构:

Action

public interface Action {
    boolean action(Context context);
}

Action实现类

public class AddCodeTemplateAction implements Action {
    @Override
    public boolean action(Context context) {
        //TODO
        return false;
    }
}

public class SaveFileAction implements Action {
    @Override
    public boolean action(Context context) {
        //TODO
        return false;
    }
}

public class TranslateCodeAction implements Action {
    @Override
    public boolean action(Context context) {
        //TODO
        return false;
    }
}

public class UploadFileAction implements Action {
    @Override
    public boolean action(Context context) {
        //TODO
        return false;
    }
}

Event:

public enum Event {
    //上传文件
    UPLOAD_FILE,
    //添加模板
    ADD_TEMPLATE,
    //转码
    TRANS_CODE,
    //保存文件
    SAVE_FILE;
}

State:

public enum State {
    //文件上传
    FILE_UPLOAD_COMPLETED,
    //转码模板
    CODE_TEMPLATE_COMPLETED,
    //转码
    TRANS_CODE_COMPLETED,
    //保存文件
    SAVE_FILE_COMPLETED;
}

ActionMapping:

package org.example.fsm;

import org.example.fsm.action.Action;
import org.example.fsm.event.Event;
import org.example.fsm.state.State;

public class ActionMapping {
    private Event event;
    private State currState;
    private State nextState;
    private Action action;

    public ActionMapping(Event event, State currState, State nextState, Action action) {
        this.event = event;
        this.currState = currState;
        this.nextState = nextState;
        this.action = action;
    }


    //根据事件和当前状态级下一个状态获取动作
    public Action getActionByEventAndState(Event event, State currState, State nextState){
        if(event == null || currState == null || nextState == null){
            return null;
        }
        else {
            if(event.equals(getEvent()) && currState.equals(getCurrState()) && nextState.equals(getNextState())){
                return getAction();
            }
            return null;
        }
    }

    //根据事件、当前状态、下一状态、动作获取ActionMapping
    public ActionMapping getActionMappingByEventAndState(Event event, State currState, State nextState,Action action){
        if(getAction().equals(action) && getEvent().equals(event) && getCurrState().equals(currState) && getNextState().equals(nextState)){
            return this;
        }
        if(getAction().equals(action) && getCurrState().equals(currState) && getNextState().equals(nextState)){
            return this;
        }
        return null;
    }


    public Event getEvent() {
        return event;
    }

    public State getCurrState() {
        return currState;
    }

    public State getNextState() {
        return nextState;
    }

    public Action getAction() {
        return action;
    }
}

Context:

package org.example.fsm;

public class Context {
    private Object params;

    public Object getParams() {
        return params;
    }

    public void setParams(Object params) {
        this.params = params;
    }
}

FSM:

package org.example.fsm;

import org.example.fsm.action.impl.AddCodeTemplateAction;
import org.example.fsm.action.impl.SaveFileAction;
import org.example.fsm.action.impl.TranslateCodeAction;
import org.example.fsm.action.impl.UploadFileAction;
import org.example.fsm.event.Event;
import org.example.fsm.state.State;

import java.util.ArrayList;
import java.util.List;

public class FSM {
    private static List<ActionMapping> actionMappingList = new ArrayList<>();

    static {
        actionMappingList.add(new ActionMapping(Event.UPLOAD_FILE, State.FILE_UPLOAD_COMPLETED,State.CODE_TEMPLATE_COMPLETED,new UploadFileAction()));
        actionMappingList.add(new ActionMapping(Event.ADD_TEMPLATE,State.CODE_TEMPLATE_COMPLETED,State.TRANS_CODE_COMPLETED,new AddCodeTemplateAction()));
        actionMappingList.add(new ActionMapping(Event.TRANS_CODE,State.TRANS_CODE_COMPLETED,State.SAVE_FILE_COMPLETED,new TranslateCodeAction()));
        actionMappingList.add(new ActionMapping(Event.SAVE_FILE,State.SAVE_FILE_COMPLETED,null,new SaveFileAction()));
    }

    public static void transform(Event event,State currState,State nextState,Context context){
        ActionMapping actionMapping = getActionMapping(event, currState, nextState);
        actionMapping.getAction().action(context);
    }

    private static ActionMapping getActionMapping(Event event,State currState,State nextState){
        for(ActionMapping actionMapping : actionMappingList){
            return actionMapping.getActionMappingByEventAndState(event, currState, nextState);
        }
        return null;
    }
}

思考当前代码结构:

1、当需要添加状态、动作、事件时需要更改三个包下的类

2、ActionMapping用来组织状态、动作、事件的关系

3、FSM中使用了策略模式,list封装了所有的策略。

当前代码结构中存在的问题:

1、违反了开闭原则

当需要添加状态和事件时,需要修改枚举类

需要增加策略时,需要修改FSM类

解决方案:

1、采用状态模式(state-pattern)

状态模式:封装基于状态的行为,并将行为委托到当前的状态。

2、设置事件顶层接口

每一个事件基于接口定义该事件的特点

将事件内聚到State中,每次一扩展时,只需要新增一整套状态、事件、动作的类

3、利用反射加注解来封装策略

 

 

 

3、自定义实现状态机(有机会实现)

  • 状态机的定义:通过定义State,Event,Action,Transition来实现状态机的流转,摒弃标准状态机中那些较复杂的概念(通过其他方式来实现所谓的较复杂的操作)
  • 状态持久化:数据持久化到数据库,实现状态机的中断重启
  • 上下文保存与传递:提供一套流程流转过程中参数的传递机制
  • 并发控制:提供不同状态机隔离,同一状态机单实例运行机制
  • 功能增强:接口或注解的形式实现自触发,重试,定时执行

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值