设计模式实战03-状态模式+建造者模式

设计模式实战03-状态模式+建造者模式

一.需求描述

在我们日常开发过程中,经常遇到单据状态扭转的需求, 比如评估单,申请单...等等,本文将基于状态机思想设计一个通用的代码框架来解决类似需求.

二.状态机简介

状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。状态机包含如下四个概念:

第一个是 State ,状态。定义一个事物所处的状况,一个状态机至少要包含两个状态。

第二个是 Event ,事件。事件就是执行某个操作的触发条件或者口令。

第三个是 Action ,动作。事件发生以后要执行动作。

第四个是 Transition ,变换(状态转移)。也就是从一个状态变化为另一个状态。定义transition就是在定义状态机的转移流程

下图是我对状态机的粗浅理解

三.状态机代码实现

通过上图分析,每个状态下可以发生多个事件, 每个事件又可以触发对应的状态转移, 在状态转移里执行定义好的Action. 通过分析可以得到一个双层MAP的状态机结构, 代码结构如下:

状态机定义了一个StateMap维护了所有的State, 每个State维护了一个event和Transition的映射关系,而每一个Transition里定义了要执行的Action以及源状态和目标状态及事件.

当触发一个事件的时候即调用StateMachine的fireEvent方法,根据源状态可以获取对应的State,然后再根据Event获取对应的Transition执行Action,执行后流转到Transition定义好的目标状态;

1.定义状态

定义状态相关接口

java 复制代码
public interface State<S, E, C>{  
   
    S getId();  
    /**  
    * @param event 触发事件  
    * @param target 目标状态  
    * @return transition  
    */  
    Transition<S, E, C> addTransition(E event, State<S, E, C> target);  
    /**  
    * @param event 触发事件  
    * @return 状态转移  
    */  
    List<Transition<S, E, C>> getEventTransitions(E event);
}

定义状态相关接口实现

java 复制代码
public class DefaultState<S, E, C> implements State<S, E, C> {  
  
    private final S stateId;  

    private final HashMap<E, List<Transition<S, E, C>>> eventTransitions = new HashMap<>();  

    DefaultState(S stateId) {  
        this.stateId = stateId;  
    }  

    @Override  
    public S getId() {  
        return stateId;  
    }  

    @Override  
    public Transition<S, E, C> addTransition(E event, State<S, E, C> target) {  
        Transition<S, E, C> transition = new Transition<>();  
        transition.setSource(this);  
        transition.setTarget(target);  
        transition.setEvent(event);  
        put(event, transition);  
        return transition;  
    }  

    @Override  
    public List<Transition<S, E, C>> getEventTransitions(E event) {  
        return eventTransitions.get(event);  
    }  
    private void put(E event, Transition<S, E, C> transition) {  
        List<Transition<S, E, C>> transitions = eventTransitions.get(event);  
        if (transitions == null) {  
            transitions = new ArrayList<>();  
            transitions.add(transition);  
            eventTransitions.put(event, transitions);  
        } else {  
            check(transitions, transition);  
            transitions.add(transition);  
        }  
    }  
    public List<Transition<S, E, C>> get(E event) {  
        return eventTransitions.get(event);  
    }  

    private void check(List<Transition<S, E, C>> existingTransitions, Transition<S, E, C> newTransition) {  
        for (Transition<S, E, C> transition : existingTransitions) {  
            if (transition.equals(newTransition)) {  
                throw new StateMachineException(newTransition + "already Exist!");  
            }  
        }  
    }
}

2.定义Action

java 复制代码
public interface Action<S, E, C> {  
  
    /**  
    *  
    * @param from 源状态  
    * @param to 目标状态  
    * @param event 事件  
    * @param context 上下文参数  
    */  
    void execute(S from, S to, E event, C context);  
}

3.定义状态转移

java 复制代码
@Getter  
@Setter
public class Transition<S, E, C> {  
  
    private final Logger logger = LoggerFactory.getLogger(Transition.class);  
    /**  
    * 动作序列
    */  
    private final List<Action<S, E, C>> actions = new ArrayList<>();  
    /**  
    * 原始状态  
    */  
    private State<S, E, C> source;  
    /**  
    * 触发事件  
    */  
    private E event;  
    /**  
    * 目标状态  
    */  
    private State<S, E, C> target;  
    /**  
    * 转移条件  
    */  
    private Predicate<C> condition;  
    /**  
    * 最后执行的异步任务  
    */  
    private Action<S, E, C> asyncTask;  
   /**  
    * 执行转移  
    */  
    public State<S, E, C> transit(C ctx) {  
        if (actions.size() != 0) {  
            for (Action<S, E, C> action : actions) {  
                action.execute(source.getId(), target.getId(), event, ctx);  
            }  
        }  
        if (asyncTask != null) {  
            CompletableFuture.runAsync(() -> asyncTask.execute(source.getId(), target.getId(), event, ctx)).exceptionally(throwable -> {  
                logger.warn("Async task run error {}", throwable.getMessage());  
                return null;  
            });  
        }  
        return target;  
    }
}

4.组装状态机

定义状态机接口

java 复制代码
public interface StateMachine<S, E, C>{
    /**  
    * 获取状态机Id   
    */  
    String getMachineId();  

    /**  
    * @param source 原始状态  
    * @param event 事件  
    * @param ctx 上下文  
    * @return target目标状态  
    */  
    S fireEvent(S source, E event, C ctx);
}

状态机实现类

java 复制代码
@Getter  
@Setter
public class StateMachineImpl<S, E, C> implements StateMachine<S, E, C> {
    //状态机State集合
    private final Map<S, State<S, E, C>> stateMap;  
    //状态机id
    private String machineId;
    
    public StateMachineImpl(Map<S, State<S, E, C>> stateMap) {  
        this.stateMap = stateMap;  
    }
    @Override  
    public S fireEvent(S source, E event, C ctx) {  
        Transition<S, E, C> transition = routeTransition(source, event, ctx);  
        if (transition == null) {  
            throw new StateMachineException("State machine route cannot find transition!");  
        }  
        return transition.transit(ctx).getId();  
    }
    
    private Transition<S, E, C> routeTransition(S source, E event, C ctx) {  
        State<S, E, C> state = getState(source);  
        List<Transition<S, E, C>> transitions = state.getEventTransitions(event);  
        if (transitions == null || transitions.size() == 0) {  
            throw new StateMachineException("State machine can find right transition!");  
        }  
        Transition<S, E, C> transit = null;  
        for (Transition<S, E, C> transition : transitions) {  
            if (transition.getCondition() == null) {  
                transit = transition;  
                break;  
            } else if (transition.getCondition().test(ctx)) {  
                transit = transition;  
                break;  
            }  
        }  
        return transit;  
    }
    
    private State<S, E, C> getState(S currentState) {  
        State<S, E, C> state = stateMap.get(currentState);  
        if (state == null) {  
            throw new StateMachineException(currentState + " is not found, please check state machine");  
        }  
        return state;  
    }
}

状态机的代码实现到这里 就差不多了,接下来我们采用建造者模式去构建状态转移和状态机

四.构建状态转移和状态机

在我们java中有一种非常优雅的代码,就是lambda表达式,采用流式编程和函数式接口来实现数据的处理,非常赏心悦目,接下来我将借鉴这种思想来实现状态转移和状态机的构建.主要是为了提供统一的构建方式;

1.构建状态机

定义接口

java 复制代码
public interface StateMachineBuilder<S, E, C> {  
  
    TransitionBuilder<S, E, C> transitionConfigurer();  

    StateMachine<S, E, C> build(String machineId);  
}

构建状态机实现

java 复制代码
public class StateMachineBuilderImpl<S, E, C> implements StateMachineBuilder<S, E, C> {  
  
    private final Map<S, State<S, E, C>> stateMap = new ConcurrentHashMap<>();  
    private final StateMachineImpl<S, E, C> stateMachine = new StateMachineImpl<>(stateMap);  

    @Override  
    public TransitionBuilder<S, E, C> transitionConfigurer() {  
        return new TransitionBuilderImpl<>(stateMap);  
    }  

    @Override  
    public StateMachine<S, E, C> build(String machineId) {  
        stateMachine.setMachineId(machineId);  
        return stateMachine;  
    }  
}

2.构建状态转移

定义构建状态转移相关接口

java 复制代码
public interface TransitionBuilder<S, E, C> {  
    //设置源状态  
    TransitionBuilder<S, E, C> from(S... stateIds);  
    //设置目标状态
    TransitionBuilder<S, E, C> to(S stateId);  
    //定义状态转移事件
    TransitionBuilder<S, E, C> on(E event);  
    //定义发生状态转移的额外条件
    TransitionBuilder<S, E, C> when(Predicate<C> condition);  
    //指定状态转移的Action
    TransitionBuilder<S, E, C> perform(Action<S, E, C> action);  
    //指定状态转移的异步Action
    TransitionBuilder<S, E, C> runAsync(Action<S, E, C> action);  
    //回到开始 的TransitionBuilder (构造一个新的Transition)
    TransitionBuilder<S, E, C> and();  

    default void end() {  
    // do nothing  
    }
}

定义状态转移实现类

java 复制代码
public class TransitionBuilderImpl<S, E, C> implements TransitionBuilder<S, E, C> {  
  
    private final Map<S, State<S, E, C>> stateMap;  
    protected State<S, E, C> target;  
    private List<State<S, E, C>> sources;  
    private List<Transition<S, E, C>> transitions;  

    public TransitionBuilderImpl(Map<S, State<S, E, C>> stateMap) {  
        this.stateMap = stateMap;  
    }  
    @Override  
    public TransitionBuilder<S, E, C> from(S... stateIds) {  
        sources = Arrays.stream(stateIds).distinct().map(stateId -> getState(stateMap, stateId)).collect(Collectors.toList());  
        return this;  
    }  

    @Override  
    public TransitionBuilder<S, E, C> to(S stateId) {  
        target = getState(stateMap, stateId);  
        return this;  
    }  

    @Override  
    public TransitionBuilder<S, E, C> on(E event) {  
        this.transitions = sources.stream().map(sources -> sources.addTransition(event, target)).collect(Collectors.toList());  
        return this;  
    }  

    @Override  
    public TransitionBuilder<S, E, C> when(Predicate<C> condition) {  
        for (Transition<S, E, C> transition : transitions) {  
            transition.setCondition(condition);  
        }  
        return this;  
    }  

    @Override  
    public TransitionBuilder<S, E, C> perform(Action<S, E, C> action) {  
        for (Transition<S, E, C> transition : transitions) {  
            transition.addAction(action);  
        }  
        return this;  
    }  

    @Override  
    public TransitionBuilder<S, E, C> runAsync(Action<S, E, C> action) {  
        for (Transition<S, E, C> transition : transitions) {  
            transition.setAsyncTask(action);  
        }  
        return this;  
    }  

    @Override  
    public TransitionBuilder<S, E, C> and() {  
        return this;  
    } 
}

这样我们在真正构建状态机的时候可以采用如下方式进行,可以很清晰判断状态扭转规则:

java 复制代码
StateMachineBuilder builder = new StateMachineBuilderImpl()
stateMachineBuilder.transitionConfigurer()
    .from(MapSettlementAuditNodeEnum.INIT)  
    .to(MapSettlementAuditNodeEnum.SELLING_MANAGER_AUDIT)  
    .on(MapSettlementEventEnum.SUBMIT)  
    .when(context -> true)  
    .perform(auditBizService.generalAction())  
    .and()
  • 首先在new StateMachineBuilderImpl的时候会创建好双层Map并放入状态机中
java 复制代码
public class StateMachineBuilderImpl<S, E, C> implements StateMachineBuilder<S, E, C> {  
  
    private final Map<S, State<S, E, C>> stateMap = new ConcurrentHashMap<>();  
    private final StateMachineImpl<S, E, C> stateMachine = new StateMachineImpl<>(stateMap);  

    @Override  
    public TransitionBuilder<S, E, C> transitionConfigurer() {  
        return new TransitionBuilderImpl<>(stateMap);  
    }  

    @Override  
    public StateMachine<S, E, C> build(String machineId) {  
        stateMachine.setMachineId(machineId);  
        return stateMachine;  
    }  
}
  • 在调用stateMachineBuilder.transitionConfigurer()会创建TransitionBuilderImpl,并传入双层Map<S, State<S, E, C>> stateMap,后续的state对应的event和transitions映射都会构造好放入这个集合中;

  • 在调用from方法定义来源状态的时候会调用一个getState方法,此方法实际上是在往stateMap新增State,并最终把构建的state集合赋值给了当前builder对象的sources字段(State集合),实现如下:

java 复制代码
@Override  
public TransitionBuilder<S, E, C> from(S... stateIds) {  
    sources = Arrays.stream(stateIds).distinct().map(stateId -> getState(stateMap, stateId)).collect(Collectors.toList());  
    return this;  
}

public static <S, E, C> State<S, E, C> getState(Map<S, State<S, E, C>> stateMap, S stateId) {  
    State<S, E, C> state = stateMap.get(stateId);  
    if (state == null) {  
        state = new DefaultState<>(stateId);  
        stateMap.put(stateId, state);  
    }  
    return state;  
}
  • 在调用to方法的时候新增State,并最终赋值给了当前builder对象的target;
java 复制代码
@Override
public TransitionBuilder<S, E, C> to(S stateId) {  
    target = getState(stateMap, stateId);  
    return this;  
}
  • 在调用on方法时,根据sources,target以及event构建transition,并放入state的eventTransitions中;并把sources当前Event件对应的transition集合赋值给当前builder对象的transitions.方便后续构造每个transition的Action和Condition;
java 复制代码
@Override
public TransitionBuilder<S, E, C> on(E event) {  
    this.transitions = sources.stream().map(sources -> sources.addTransition(event, target)).collect(Collectors.toList());  
    return this;  
}
  • 在调用when方法时, 定义转移条件,在调用perform则设置transition的行为;
java 复制代码
@Override  
public TransitionBuilder<S, E, C> when(Predicate<C> condition) {  
    for (Transition<S, E, C> transition : transitions) {  
        transition.setCondition(condition);  
    }  
    return this;  
}  
  
@Override  
public TransitionBuilder<S, E, C> perform(Action<S, E, C> action) {  
    for (Transition<S, E, C> transition : transitions) {  
        transition.addAction(action);  
    }  
    return this;  
}
  • 整个过程结束,State的event,transition关系映射已构建;

五.状态机使用

下面是状态机具体业务使用案例

1.配置状态机Bean

java 复制代码
@Configuration  
public class StateMachineConfig {
    private final MapSettlementAuditBizService auditBizService;  

    public StateMachineConfig(MapSettlementAuditBizService mapSettlementAuditBizService) {  
        this.auditBizService = mapSettlementAuditBizService;  
    }
    
    @Bean("mapSettlementStateMachine")  
    public StateMachine<MapSettlementAuditNodeEnum, MapSettlementEventEnum, MapSettlementContext> stateMachine() {
        StateMachineBuilder<MapSettlementAuditNodeEnum, MapSettlementEventEnum, MapSettlementContext> stateMachineBuilder = new StateMachineBuilderImpl<>();
        stateMachineBuilder.transitionConfigurer()  
            .from(MapSettlementAuditNodeEnum.INIT)  
            .to(MapSettlementAuditNodeEnum.SELLING_MANAGER_AUDIT)  
            .on(MapSettlementEventEnum.SUBMIT)  
            .when(context -> true)  
            .perform(auditBizService.generalAction())  
            .and()  
            .from(MapSettlementAuditNodeEnum.SELLING_MANAGER_AUDIT)  
            .to(MapSettlementAuditNodeEnum.SELLING_ASSISTANT_AUDIT)  
            .on(MapSettlementEventEnum.PASS)  
            .when(context -> true)  
            .perform(auditBizService.generalAction())  
            .and()
            ...
            end();
     return stateMachineBuilder.build("mapSettlementStateMachine");
    }
    
    //需要构建其他状态机,在下面再增加一个Bean就可以了
}

2.调用状态机方法

java 复制代码
//获取业务单据当前状态, 当前事件,传入上下文参数
MapSettlementAuditNodeEnum targetNode = stateMachine.fireEvent(currentAuditNode, MapSettlementEventEnum.SUBMIT, settlementContext);

六.总结

当遇到涉及状态转移,节点审批等需求的时候,可以考虑状态机模式,可以提供一个通用的代码模版, 其主要作用让业务逻辑清晰可见,可维护性高,同时规范了代码行为.

本文的状态机实现不支持动态变更转移规则,适合不是特别复杂的业务需求. 目前面对特别复杂的流程审批等需求,一般都是采用成熟流程引擎平台,本文就不做拓展了;

相关推荐
丶白泽14 分钟前
重修设计模式-设计原则
设计模式·接口隔离原则·依赖倒置原则·开闭原则
【D'accumulation】17 分钟前
典型的MVC设计模式:使用JSP和JavaBean相结合的方式来动态生成网页内容典型的MVC设计模式
java·设计模式·mvc
wn53127 分钟前
【Go - 类型断言】
服务器·开发语言·后端·golang
希冀1231 小时前
【操作系统】1.2操作系统的发展与分类
后端
GoppViper1 小时前
golang学习笔记29——golang 中如何将 GitHub 最新提交的版本设置为 v1.0.0
笔记·git·后端·学习·golang·github·源代码管理
仙魁XAN1 小时前
Unity 设计模式 之 创造型模式-【工厂方法模式】【抽象工厂模式】
unity·设计模式·工厂方法模式·抽象工厂模式
爱上语文2 小时前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people2 小时前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
罗政8 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
拾光师9 小时前
spring获取当前request
java·后端·spring