临沭县哪里有建网站的,东莞保安公司哪家好,wordpress线下安装教程视频,大连市住建局官方网【Alibaba Cola 状态机】重点解析以及实践案例
1. 状态模式
状态模式是一种行为型设计模式#xff0c;允许对象在内部状态改变时改变其行为#xff0c;简单地讲就是#xff0c;一个拥有状态的context对象#xff0c;在不同状态下#xff0c;其行为会发生改变。看起来是改…【Alibaba Cola 状态机】重点解析以及实践案例
1. 状态模式
状态模式是一种行为型设计模式允许对象在内部状态改变时改变其行为简单地讲就是一个拥有状态的context对象在不同状态下其行为会发生改变。看起来是改变了对象各个接口方法的实现一样。
模式中包含角色
上下文抽象状态具体状态
优点包括解耦客户端和状态对象可扩展性强避免大量条件语句。
缺点是可能增加系统类的数量和复杂性。
适用场景如自动售货机的状态转换、线程状态管理等。代码示例展示了自动售卖机如何利用状态模式实现不同状态的切换。
推荐文章23种设计模式之状态模式State Pattern_状态模式哪种状态切换好-CSDN博客
2. 状态机
状态机Finite State Machine简称FSM是一个数学模型用于描述对象在其生命周期中可能的状态以及状态之间的转换。
它由一组状态、一组事件、一组转换规则和一组动作组成能够清晰地表示和管理对象的行为和状态变化。
在状态机中“有限”指的是状态和事件都是有限的即存在一个有限的状态集和事件集。这使得状态机具有明确的、可控的行为模式便于分析和实现。 状态机的组成元素状态、事件、转换、动作
状态State: 表示对象的当前情况或条件。状态机中的状态是有限的例如待机、运行、暂停、停止等。
事件Event: 触发状态转换的输入或信号。事件可以是外部输入、内部触发或其他系统生成的信号。
转换Transition: 定义了状态之间的切换规则。当特定的事件发生时状态机会根据转换规则从一个状态转换到另一个状态。
动作Action: 在状态转换过程中执行的操作或任务。动作可以是状态进入前执行的预处理、状态转换时执行的中间操作或状态退出后执行的清理工作。
状态机的优缺点
优点
逻辑清晰状态机将复杂的系统行为分解为简单的状态和状态之间的转换使系统逻辑更加清晰和易于理解。可维护性高由于状态机的逻辑是模块化的任何状态的修改或扩展只需在该状态的实现部分进行不会影响其他部分增强了代码的可维护性。可扩展性强新的状态和转换可以方便地添加到现有的状态机中使系统具有良好的扩展性。
缺点状态爆炸对于复杂系统状态和转换的数量可能非常庞大导致状态机图变得复杂难以管理这被称为“状态爆炸”问题。
3. Alibaba Cola 状态机
实现一个状态机引擎教你看清DSL的本质_状态机 dsl-CSDN博客
相比Spring statemachine状态机等的复杂功能多
但是我们 实际业务员 需要常用的功能简单使用所以这类就显得不简洁再看cola-statemachine相比就是小巧、无状态、简单、轻量、性能极高的状态机DSL实现解决业务中的状态流转问题。
如果是实现业务的话阿里状态机是不二之选更加适合我们日常开发KISSKeep It Simple and Stupid 而如果是比较复杂的业务或者组件开发这个状态机可能也能 妙用进行应对面对不了可能就得用 Spring statemachine 有状态的状态机或者对阿里状态机进行 二次开发 开源者的初心就是让开发更舒适结合实际需求抽离出必要部分而不是严格按照传统状态模式传统状态机
无状态状态机–cola stateMachine-CSDN博客
状态机本身没有状态而是提供一个状态机单例通过输入参数 “起始状态”、“事件”、“上下文”这些参数足以确定一次状态轮转期间通过 condition 和 action 后返回“最终状态”若轮转失败则返回原状态
由于没有状态所以调用者使用这个状态机都是互不干扰的通过输入决定输出一次轮转做到一个实例服务多个调用者
这里的上下文就是过程中的通行数据罢了你也可以在上下文里面去设置状态像状态模式那样当其实在使用状态机的时候状态机就相当于状态模式的上下文这样看发挥状态机的优势就没必要设置了。
采用了无状态设计之后我们就可以使用一个状态机 Instance 来响应所有的请求了性能会大大的提升 原本的状态机是有状态的状态机当前的状态决定了其行为这样导致每次请求都是申请一个新的状态机去维护状态的轮转轮转的过程状态机的状态也在时刻发生变化 当阿里状态机理念是状态机每次进行一次轮转就行了哪怕需要连续的过程性的轮转多次请求状态机即可一个轮转的 action 嵌套执行外部轮转的事件返回的最终状态可能与实际不符最好嵌套执行内部轮转而不是外部轮转或者等轮转结束再按照这次请求的响应进行下次轮转 有状态的状态机就相当于可以本地运行的应用不同客户之间是隔离的状态在应用内部轮转 无状态的状态机就相当于 web 应用所有客户访问同一个 web请求一次执行一次特定的轮转响应结果 4. Alibaba Cola 状态机使用
源码其实不难大部分还是容易看懂的如果出现问题按照思路可以调试去查查这里就是我总结的用法。 /*** Alibaba Cola 状态机关键词* State状态* Event事件状态由事件触发引起变化* Transition流转表示从一个状态到另一个状态* External Transition外部流转两个不同状态之间的流转* Internal Transition内部流转同一个状态之间的流转* Condition条件表示是否允许到达某个状态* Action动作到达某个状态之后可以做什么* StateMachine状态机*/4.1 StateMachineUtil核心方法封装
Slf4j
public class StateMachineUtil {public static S, E, C StateMachineS, E, C getMachine(String machineId) {return StateMachineFactory.get(machineId);}public static void showMachine(String machineId) {getMachine(machineId).showStateMachine();}public static String generatePlantUML(String machineId) {return getMachine(machineId).generatePlantUML();}public static S, E, C void printMachine(String machineId) {StateMachineS, E, C stateMachine getMachine(machineId);stateMachine.showStateMachine();System.out.println(stateMachine.generatePlantUML());}public static S, E, C S fireEvent(String machineId, S state, E event, C context) {return (S) getMachine(machineId).fireEvent(state, event, context);}}通过 machineId 获得状态机实例StateMachineFactory.get(machineId)打印状态机注意重写 toString 方法到想要的效果执行状态机machineId起始状态事件上下文返回最终状态
4.2 状态机准备信息
根据状态机轮转所需的信息也就是准备信息在状态机构建过程中需要用到
我将其抽象成接口有两个外部流转助手、内部流转助手
状态的事件会让状态变成别的状态还是不变就是这里外和内
public interface StateExternalTransitionHelperS, E, C {ListS getFromState();S getToState(S from) throws GlobalServiceException;E getOnEvent();ConditionC getWhenCondition();ActionS, E, C getPerformAction();}from 为 list 是因为有时候同一事件多个状态都可以执行其中 getToState 通过 from 得知 to并允许抛出异常
public interface StateInternalTransitionHelperS, E, C {ListS getWithinList();E getOnEvent();ConditionC getWhenCondition();ActionS, E, C getPerformAction();
}4.3 通过准备信息构造状态机
外部流转助手、内部流转助手 的实现类就蕴含了准备信息将这些在状态机构造过程中应用
private static S, E, C void builderAssign(StateMachineBuilderS, E, C builder,StateExternalTransitionHelperS, E, C helper) {ListS fromStateList helper.getFromState();if(!CollectionUtil.isEmpty(fromStateList)) {E onEvent helper.getOnEvent();ConditionC whenCondition helper.getWhenCondition();ActionS, E, C performAction helper.getPerformAction();fromStateList.forEach(fromState - {try {S toState helper.getToState(fromState); // 若抛异常就忽略这一个构造下一个状态轮转// 不保证每个 toState 相等的情况下不用 externalTransitions 与 fromAmongbuilder.externalTransition().from(fromState).to(toState).on(onEvent).when(whenCondition).perform(performAction);} catch (GlobalServiceException e) {log.warn(e.getMessage());}});}
}private static S, E, C void builderAssign(StateMachineBuilderS, E, C builder,StateInternalTransitionHelperS, E, C helper) {ListS withinList helper.getWithinList();if(!CollectionUtil.isEmpty(withinList)) {E onEvent helper.getOnEvent();ConditionC whenCondition helper.getWhenCondition();ActionS, E, C performAction helper.getPerformAction();withinList.forEach(within - {builder.internalTransition().within(within).on(onEvent).when(whenCondition).perform(performAction);});}
}public static S, E, C void buildMachine(String machineId,List? extends StateExternalTransitionHelperS, E, C externalHelpers,List? extends StateInternalTransitionHelperS, E, C internalHelpers) {// 创建一个 builderStateMachineBuilderS, E, C builder StateMachineBuilderFactory.create();// 添加轮转externalHelpers.forEach(helper - {builderAssign(builder, helper);});internalHelpers.forEach(helper - {builderAssign(builder, helper);});// 创建状态机builder.build(machineId);
}4.4 示例简历状态轮转
不要直接实现那两个接口要先用模块内部的接口继承这样注入 Spring 容器中的 bean获取的时候指定我们自己的接口类型防止获得别的模块的 helper bean
public interface ResumeStateExternalTransitionHelper extends StateExternalTransitionHelperResumeStatus, ResumeEvent, ResumeContext {}public interface ResumeStateInternalTransitionHelper extends StateInternalTransitionHelperResumeStatus, ResumeEvent, ResumeContext {}上下文
Getter
Setter
Builder
AllArgsConstructor
NoArgsConstructor
Slf4j
public class ResumeContext {private Long managerId;private StuResume resume;ResumeExecuteDTO executeDTO;public void log(ResumeStatus from, ResumeStatus to, ResumeEvent event) {log.info(resume state from {} to {} run {} currentResume {} managerId {} executeDTO {},from, to, event, resume.getId(), managerId, executeDTO);}}简历状态
Getter
public enum ResumeStatus {DRAFT(草稿, 0),PENDING_SELECTION(待筛选, 1),REJECTED(筛选不通过, 2),SCHEDULE_INITIAL_INTERVIEW(待安排初试, 3),PENDING_INITIAL_INTERVIEW(待初试, 4),INITIAL_INTERVIEW_PASSED(初试通过, 5), // 仅当初试为最后一个流程时显示INITIAL_INTERVIEW_FAILED(初试不通过, 6), // 仅当初试为最后一个流程时显示SCHEDULE_SECOND_INTERVIEW(待安排复试, 7),PENDING_SECOND_INTERVIEW(待复试, 8),SECOND_INTERVIEW_PASSED(复试通过, 9), // 仅当复试为最后一个流程时显示SECOND_INTERVIEW_FAILED(复试不通过, 10), // 仅当复试为最后一个流程时显示SCHEDULE_FINAL_INTERVIEW(待安排终试, 11),PENDING_FINAL_INTERVIEW(待终试, 12),FINAL_INTERVIEW_PASSED(终试通过, 13), // 仅当复试为最后一个流程时显示FINAL_INTERVIEW_FAILED(终试不通过, 14), // 仅当复试为最后一个流程时显示PENDING_HANDLING(待处理, 15),SUSPENDED(挂起, 16),;ResumeStatus(String message, Integer code) {this.message message;this.code code;}Overridepublic String toString() {return message;}private final String message;EnumValueJsonValueprivate final Integer code;public static ResumeStatus get(Integer code) {for (ResumeStatus resumeStatus : ResumeStatus.values()) {if(resumeStatus.getCode().equals(code)) {return resumeStatus;}}throw new GlobalServiceException(GlobalServiceStatusCode.USER_RESUME_STATUS_EXCEPTION);}
}简历事件
Getter
public enum ResumeEvent {NEXT(1, 推进),APPROVE(2, 通过),ELIMINATE(3, 淘汰),RESET(4, 重置),PENDING(5, 待处理),SUSPEND(6, 挂起),CONFIRM(7, 转正),;Overridepublic String toString() {return description;}private final Integer event;private final String description;ResumeEvent(Integer event, String description) {this.event event;this.description description;}public static ResumeEvent get(Integer event) {for (ResumeEvent resumeEvent : ResumeEvent.values()) {if(resumeEvent.getEvent().equals(event)) {return resumeEvent;}}throw new GlobalServiceException(GlobalServiceStatusCode.USER_RESUME_STATUS_TRANS_EVENT_ERROR);}}你也可以不是枚举类但是我们要提供有限的固定的实例并且代表状态和事件枚举太适用了
常量类
public interface ResumeStateMachineConstants {String RESUME_STATE_MACHINE_ID resumeStateMachineId;}配置类
Configuration
RequiredArgsConstructor
public class ResumeStateMachineBuildConfig {private final ListResumeStateExternalTransitionHelper externalHelpers;private final ListResumeStateInternalTransitionHelper internalHelpers;PostConstructpublic void buildInterviewMachine() {StateMachineUtil.buildMachine(ResumeStateMachineConstants.RESUME_STATE_MACHINE_ID,externalHelpers,internalHelpers);StateMachineUtil.printMachine(ResumeStateMachineConstants.RESUME_STATE_MACHINE_ID);}}一个 helper 实现展示
Component
RequiredArgsConstructor
public class ResumeNextStateHelper implements ResumeStateExternalTransitionHelper {private final ConditionResumeContext defaultResumeCondition;private final ActionResumeStatus, ResumeEvent, ResumeContext defaultResumeAction;Overridepublic ListResumeStatus getFromState() {return List.of(DRAFT,PENDING_SELECTION,SCHEDULE_INITIAL_INTERVIEW,PENDING_INITIAL_INTERVIEW,SCHEDULE_SECOND_INTERVIEW,PENDING_SECOND_INTERVIEW,SCHEDULE_FINAL_INTERVIEW);}Overridepublic ResumeStatus getToState(ResumeStatus from) throws GlobalServiceException {return switch (from) {case DRAFT - PENDING_SELECTION;case PENDING_SELECTION - SCHEDULE_INITIAL_INTERVIEW;case SCHEDULE_INITIAL_INTERVIEW - PENDING_INITIAL_INTERVIEW;case PENDING_INITIAL_INTERVIEW - SCHEDULE_SECOND_INTERVIEW;case SCHEDULE_SECOND_INTERVIEW - PENDING_SECOND_INTERVIEW;case PENDING_SECOND_INTERVIEW - SCHEDULE_FINAL_INTERVIEW;case SCHEDULE_FINAL_INTERVIEW - PENDING_FINAL_INTERVIEW;default - throw new GlobalServiceException(GlobalServiceStatusCode.USER_RESUME_STATUS_EXCEPTION);};}Overridepublic ResumeEvent getOnEvent() {return ResumeEvent.NEXT;}Overridepublic ConditionResumeContext getWhenCondition() {return defaultResumeCondition;}Overridepublic ActionResumeStatus, ResumeEvent, ResumeContext getPerformAction() {return defaultResumeAction;}
}这里这两个是我写的默认值并注入了容器这样的写法需要注意 bean 的名称不要冲突了 通过以上代码在项目启动的时候即可构造出状态机 执行状态机代码片段 我个人觉得没必要在状态机内部将状态落库根据请求状态机的响应进行落库
5. 小思考 · 状态机设计的合理性
状态机并不是通过 from 和 to 确定 event而是 from 和 event 确定轮转
通过 from 到 to 的变化是确定不了事件的实现通过 from 到 to 的变化触发某一事件事件可能不止一个而且并不保证触发我们想要的那一个
比如一次请求对象的当前属性就是 from请求是以 event 作为参数还是以 to 作为参数
① 若是 event 作为参数那么根据 from 和 event 即可进行状态轮转并返回最终的状态
② 若是 to 作为参数from 到 to 的这个状态变化会触发什么特定行为
状态机更注重的是状态的轮转也就是 ①而 ② 是状态变化的后置行为 以“一场面试”为例一场面试的状态可以是 未开始 、 进行中 、 已结束 如果是 ①传入的参数可以是 “开始面试” 这一具体事件当前面试状态是 ”未开始“就可以轮转成”进行中“也可以是 “通知用户” 这一事件状态内部流转的过程中对用户进行面试通知 如果是 ②传入的参数可以是 ”进行中“面试状态将直接更新成”进行中“状态变化触发对应的事件但做不了内部轮转 ① 这种方式靠的是状态机让请求行为更加具体如进行“开始面试”“通知用户”这种具体的行为
而 ② 更加狭隘如果我们设计一份代码来管理 from 到 to 触发的事件如果要符合我们的接口预期这往往可能 并不能做到 ① 的可读性高、灵活性高、通用性高、可扩展性高 用 ② 来进行状态轮转不太合适但是也不是完全没用更适合用其管理一些状态变化固定的“副作用”作为状态轮转的后置行为还是不错的可以结合责任链模式去实现 但是又说回来from 到 to 的转变的副作用在编写代码的时候应该也就是规定对应事件的副作用吧比如 “未开始” 到 “进行中”就是“面试开始” 这一具体行为的副作用那其实在状态机就能实现啊 也就是说 ② 这种方式适合有多个事件可以进行 from 到 to 的轮转并且这些事件都有共同的副作用这个副作用就可以用 ② 来管理出现这种情况也很极端了~ 在比较简单的系统from 和 to 能够 可以确定唯一的事件没有内部轮转的需求 用这个也无所谓 如果非要 ② 这种请求方式不想指定 event出现特殊的状态变化就得自动触发对应事件这种就属于特殊需求了 from 到 to 触发的事件的映射关系也只能在 to 已知的情况下可以复用甚至只用于这一特殊请求代码写得可能比较死不太优雅太为难了(′⌒)反正我不是很喜欢我个人比较喜欢优雅直观的设计 我个人认为要是有个接口可以任意改变状态那么应该不需要触发什么特定事件了毕竟都允许可以任意更改了或者说有一个统一的事件 可以定义一个”任意更改状态“的事件上下文携带 toState所有状态都可以触发在状态机的内部/外部轮转状态的行为即可 但这不符合状态机明确的设计理念 综上所述
用 ① 更加合适可以让请求行为更加优雅具体直观和灵活开发低耦合更具有扩展性能实现的功能更多② 的这种请求方式适合那种状态变化没有任何事件触发的场景或触发的都是同一事件的场景等不具有状态轮转的过程概念的场景。
BUT
规矩是死的视具体需求而论切勿只纸上谈兵
以上也只是我的想法不同人有不同的想法和理念
状态机还可以结合很多其他的设计模式更多妙用等你开发
状态机你爱咋用咋用只要合理都 OK 啦
开发是灵活的过程开发者的理念为准没有固定的强限定但是我们要满足其基本的优秀写法