跳至主要內容

【设计模式】行为型-⑤状态模式

holic-x...大约 11 分钟设计模式设计模式

【设计模式】行为型-⑤状态模式

学习核心

  • 状态模式核心
    • 概念:状态模式描述的是一个行为下的多种状态变更
    • 组成:
      • 状态枚举(Enum<Status>):定义业务流程中涉及到的状态
      • 抽象状态类(State):定义状态流转的操作方法
        • 对于一些场景,如果状态流转操作方法类似,也会考虑只定义一个方法进行简化,然后各自子类业务逻辑进行丰富,此处主要是让职责更清晰,所以拆分了多个流转方法
      • 具体状态类(xxxState):继承抽象状态类,实现当前状态对应操作方法的流转逻辑(如果不涉及当前状态操作则可忽略不实现或者提示操作非法)
      • 状态服务类(StateHandler):提供统一的状态流转服务管理,维护了状态枚举和具体状态类的关联,通过其提供的方法入口调用相应的流转方法
  • 应用场景分析
    • 【BPM流程】审核状态流转场景:营销活动上线的状态流转

概念核心

​ 状态模式:描述的是一个行为下的多种状态变更。它是以状态为作为处理核心,拆解每个状态的出和入,据此来进行状态流转

​ 实现核心:

  • 拆解流程中涉及的的状态
  • 构建抽象状态类,定义状态流转方法规范
  • 构建具体状态类,填充状态流转逻辑
  • 构建状态服务管理类,定义统一状态流转的操作入口

场景案例分析

1.【营销活动】审核状态流转场景

核心:营销活动、多级审核、状态流转、流程控制

​ 【营销活动】审核状态流转场景:一个活动的上线是需要多个层级审核上线的。流程节点中包括了各个状态到下个状态扭转的关联条件,此处的场景处理就是要完成这些状态的转变。

​ 审核类的业务场景是一个比较常见的开发场景,当对活动或者配置进行修改后需要审核通过才能对外发布,⽽这 个审核的过程往往会随着系统的重要程度⽽设⽴多级控制,来保证⼀个活动可以安全上线,避免造成资源损耗。 一些场景中会用到审批流的过程配置,也是⾮常⽅便开发类似的流程的,也可以在配置中设定某个 节点的审批⼈员。但此处主要体现的点在于模拟学习对⼀个活动/任务/流程的多个状态节点的审核控制

🎃活动状态流转分析(状态变更)

​ 针对流程管理类相关设计,可能涉及很多记录状态的流转、变更,一开始分析可能会有点懵,包括自己在一开始接触这种状态流转概念的时候,经常会被每个状态可能是由什么状态转过来的、又可以转变为什么状态搞的晕头转向,但接触过流程管理相关系统的开发,了解相关流程引擎的工作原理和思路,在针对一些流程类业务开发的时候,要先抓住业务流程类开发的重点是“流转处理”,而记录的流转则是由状态节点一个个串联起来的,因此在梳理流程状态的时候可以尝试以下思路(以lottery活动流转为例进行说明)

1.先梳理业务流程,然后分析涉及的流程状态节点

​ 查看、编辑、提审、撤审、通过、拒绝、关闭、开启、执行

image-20240924105251581

2.分析流程状态节点可以执行的操作(当前的节点状态的流程可以执行的下一步操作是什么(可以变更的target状态是什么))

​ 此处不要将"当前状态节点可能是由什么状态转过来"纳入分析,因为这会让自己处于混沌状态,也是流程开发的一个小误区。当确定了流程开发的步骤(业务流程),其相应的节点状态也确定下来,因此只需要根据流程节点状态的流转走便能形成"回路",将重点侧重于"当前状态可以执行什么操作、变成下一个targetStatus"

​ 反过来想,之所以一开始会考虑某个节点状态是由什么转过来的,也是基于业务校验的一个考虑,担心存在不符合流转规则的数据,但如果能够在流转的过程中去控制(”校验流转状态变更,从而限制入口“),这个问题也就不复存在(流转规则制定、流转过程校验)

​ 而流转规则的指定则需结合实际业务考虑,例如针对一些复杂的业务,某个节点状态又可根据不同的情况限制相应的操作

​ 此处则需区分“状态流转”和“业务功能限制”,“状态流转”只需考虑当前节点能否流转到下一节点,而“业务功能限制”则需考虑当前节点的上一节点是什么,可以执行什么操作(可限制功能访问甚至是限制下一节点的流转),简单举例说明

image-20240924105336252

3.根据每个流转状态节点,制定相应的方法供状态变更

​ 以营销活动的审核流程场景为例,说明如下所示:依据流程分析每个状态节点的状态和流转,只考虑当前节点状态的出入分析。针对某个状态节点只考虑“出”的情况,即由当前节点和可以执行什么操作(变更为指定状态),“入”的情况则可在其他状态中体现

  • 编辑态

image-20240924105525974

  • 提审态

image-20240924105541263

  • 撤审态

image-20240924105552074

  • 通过态

image-20240924105603105

  • 拒绝态

image-20240924105612937

  • 关闭态

image-20240924105626423

  • 开启态

image-20240924105639847

  • 执行态

image-20240924105648899

✨传统实现方式

​ 针对状态流转场景,最基础的实现方式就是梳理状态流转的关系,通过设定流程状态,根据当前流程状态判断当前节点操作人员可执行的操作

参考代码实现

  • 活动信息实体定义
/**
 * 活动信息
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ActivityInfo {
    private String activityId; // 活动ID
    private String activityName; // 活动名称
    private Enum<ActivityStatus> status; // 活动状态
    private Date beginTime; // 活动开始时间
    private Date endTime; // 活动结束时间
}
  • 活动服务定义(提供状态流转机制:校验、流转)


/**
 * 活动服务定义
 */
public class ActivityService {

    /**
     * 校验状态有效性
     *
     * @param currentStatus
     * @param afterStatus
     * @return
     */
    private static boolean validOper(ActivityStatus currentStatus, ActivityStatus afterStatus) {

        /**
         * 活动状态变更约定:
         * 1.编辑      ->提审、关闭
         * 2.提审      ->撤审、通过、拒绝、关闭
         * 3.撤审      ->编辑
         * 4.通过      ->活动中
         * 5.拒绝      ->撤审
         * 6.关闭      ->编辑、开启
         * 7.开启      ->关闭、活动中
         * 8.活动中    ->关闭
         * 如果不满足约定则视为非法操作
         */
        if (ActivityStatus.Editing == currentStatus) {
            // 编辑      ->提审、关闭
            return ActivityStatus.Aduit == afterStatus || ActivityStatus.Close == afterStatus;
        } else if (ActivityStatus.Aduit == currentStatus) {
            // 提审      ->撤审、通过、拒绝、关闭
            return ActivityStatus.CancelAduit == afterStatus || ActivityStatus.Pass == afterStatus || ActivityStatus.Refuse == afterStatus || ActivityStatus.Close == afterStatus;
        } else if (ActivityStatus.CancelAduit == currentStatus) {
            // 撤审      ->编辑
            return ActivityStatus.Editing == afterStatus;
        } else if (ActivityStatus.Pass == currentStatus) {
            // 通过      ->活动中
            return ActivityStatus.Doing == afterStatus;
        } else if (ActivityStatus.Refuse == currentStatus) {
            // 拒绝      ->撤审
            return ActivityStatus.CancelAduit == afterStatus;
        } else if (ActivityStatus.Close == currentStatus) {
            // 关闭      ->编辑、开启
            return ActivityStatus.Editing == afterStatus || ActivityStatus.Open == afterStatus;
        } else if (ActivityStatus.Open == currentStatus) {
            // 开启      ->关闭、活动中
            return ActivityStatus.Close == afterStatus || ActivityStatus.Doing == afterStatus;
        } else if (ActivityStatus.Doing == currentStatus) {
            // 开启      ->关闭、活动中
            return ActivityStatus.Close == afterStatus;
        } else {
            System.out.println(currentStatus + "状态非法");
            return false;
        }
    }

    /**
     * 执行状态变更
     *
     * @param activityId
     * @param currentStatus
     * @param afterStatus
     */
    public static synchronized void execStatus(String activityId, ActivityStatus currentStatus, ActivityStatus afterStatus) {
        // 也可以模拟根据活动ID查找对应的状态 ActivityStatus currentStatus = ActivityStatus.Check;
        // todo 业务场景中需判断当前状态,校验状态变更是否符合约定 (例如A->B的状态变更是否合理),此处作为扩展项
        System.out.println("模拟校验状态变更有效性:" + activityId + "活动状态变更-" + currentStatus + "=>" + afterStatus);
        boolean validOperFlag = validOper(currentStatus, afterStatus);
        if (validOperFlag) {
            // 进行状态流转
            System.out.println("状态变更成功:" + afterStatus);
        } else {
            System.out.println("状态表更操作非法,拒绝操作!");
        }
    }

}
  • 客户端测试
/**
 * 活动客户端测试demo
 */
public class ActivityClient {
    public static void main(String[] args) {
        // 模拟活动状态流转场景
        ActivityService activityService = new ActivityService();
        activityService.execStatus("1", ActivityStatus.Editing, ActivityStatus.Doing);
        System.out.println("----------");
        activityService.execStatus("1", ActivityStatus.Aduit, ActivityStatus.Pass);
        System.out.println("----------");
        activityService.execStatus("1", ActivityStatus.Doing, ActivityStatus.Refuse);
    }
}
-- output
模拟校验状态变更有效性:1活动状态变更-Editing=>Doing
状态表更操作非法,拒绝操作!
----------
模拟校验状态变更有效性:1活动状态变更-Aduit=>Pass
状态变更成功:Pass
----------
模拟校验状态变更有效性:1活动状态变更-Doing=>Refuse
状态表更操作非法,拒绝操作!

✨状态模式

开发思路

  • 梳理流程中涉及到的节点状态,每个状态都是一个流转处理点(Editing,Aduit,CancelAduit,Pass,Refuse,Close,Open,Doing
  • 构建抽象状态类,定义状态流转涉及的相关方法(即活动状态从一个状态到另一个状态的触发操作,例如审核通过就是将活动状态从待审核到审核成功)
    • 从实现上看每个方法好像功能都是一样的,也可以定义一个公共的方法,然后在各自的逻辑中进行区分,此处是为了更好体现职责分离进行拆分
  • 定义具体状态类,继承抽象状态类,并实现具体的方法逻辑。对于非当前状态处理的方法则可提示非法操作
  • 定义状态处理服务:提供对状态服务的统一控制中心,将状态枚举和对应的状态处理类进行关联,并对外提供操作入口进行统一处理
  • 定义测试客户端:测试相关状态流转的正确性

参考代码

  • 抽象状态类(State,父类,抽象类)
/**
 * 状态抽象类:定义状态流转涉及的相关方法
 */
public abstract class State {

    // 活动提审
    public abstract Result aduit(String activityId, Enum<ActivityStatus> currentStatus);

    // 审核通过
    public abstract Result checkPass(String activityId, Enum<ActivityStatus> currentStatus);

    // 审核拒绝
    public abstract Result checkRefuse(String activityId, Enum<ActivityStatus> currentStatus);

    // 撤审
    public abstract Result checkRevoke(String activityId, Enum<ActivityStatus> currentStatus);

    // 活动关闭
    public abstract Result close(String activityId, Enum<ActivityStatus> currentStatus);

    // 活动开启
    public abstract Result open(String activityId, Enum<ActivityStatus> currentStatus);

    // 活动执行
    public abstract Result doing(String activityId, Enum<ActivityStatus> currentStatus);

}
  • 具体状态类(XXState,子类,继承抽象状态类,并实现对应状态的流转逻辑)
/**
 * 编辑状态:
 * 可由编辑状态->提审、关闭,其他状态都是非法的
 */
public class EditingState extends State {
    @Override
    public Result aduit(String activityId, Enum<ActivityStatus> currentStatus) {
        System.out.println( activityId + "活动状态变更 from " + currentStatus + " to " + ActivityStatus.Aduit );
        return Result.SUCCESS;
    }

    @Override
    public Result checkPass(String activityId, Enum<ActivityStatus> currentStatus) {
        return Result.ILLEGAL;
    }

    @Override
    public Result checkRefuse(String activityId, Enum<ActivityStatus> currentStatus) {
        return Result.ILLEGAL;
    }

    @Override
    public Result checkRevoke(String activityId, Enum<ActivityStatus> currentStatus) {
        return Result.ILLEGAL;
    }

    @Override
    public Result close(String activityId, Enum<ActivityStatus> currentStatus) {
        System.out.println( activityId + "活动状态变更 from " + currentStatus + " to " + ActivityStatus.Close );
        return Result.SUCCESS;
    }

    @Override
    public Result open(String activityId, Enum<ActivityStatus> currentStatus) {
        return Result.ILLEGAL;
    }

    @Override
    public Result doing(String activityId, Enum<ActivityStatus> currentStatus) {
        return Result.ILLEGAL;
    }
}

/**
 * 提审状态:
 * 可由提审状态->撤审、通过、拒绝、关闭
 */
public class AduitState extends State{
    @Override
    public Result aduit(String activityId, Enum<ActivityStatus> currentStatus) {
        return Result.ILLEGAL;
    }

    @Override
    public Result checkPass(String activityId, Enum<ActivityStatus> currentStatus) {
        System.out.println( activityId + "活动状态变更 from " + currentStatus + " to " + ActivityStatus.Pass );
        return Result.SUCCESS;
    }

    @Override
    public Result checkRefuse(String activityId, Enum<ActivityStatus> currentStatus) {
        System.out.println( activityId + "活动状态变更 from " + currentStatus + " to " + ActivityStatus.Refuse );
        return Result.SUCCESS;
    }

    @Override
    public Result checkRevoke(String activityId, Enum<ActivityStatus> currentStatus) {
        return null;
    }

    @Override
    public Result close(String activityId, Enum<ActivityStatus> currentStatus) {
        System.out.println( activityId + "活动状态变更 from " + currentStatus + " to " + ActivityStatus.Close );
        return Result.SUCCESS;
    }

    @Override
    public Result open(String activityId, Enum<ActivityStatus> currentStatus) {
        return Result.ILLEGAL;
    }

    @Override
    public Result doing(String activityId, Enum<ActivityStatus> currentStatus) {
        return Result.ILLEGAL;
    }
}
  • 状态服务控制类(StateHandler),统一状态服务流转控制入口,通过Map将状态枚举ActivityStatus和对应处理器xxxState关联起来
/**
 * 状态处理服务(提供对状态服务的统一控制中心)
 */
public class StateHandler {

    // 定义状态列表和对应的event映射
    private Map<Enum<ActivityStatus>, State> stateMap = new HashMap<Enum<ActivityStatus>, State>();

    // 初始化状态列表
    public StateHandler(){
        stateMap.put(ActivityStatus.Editing,new EditingState());
        stateMap.put(ActivityStatus.Aduit,new AduitState());
        // ...... 其他状态定义扩展 ......
    }

    // ---------- 提供统一的处理方法 ----------

    // 活动提审
    public Result aduit(String activityId, Enum<ActivityStatus> currentStatus){
        return stateMap.get(currentStatus).aduit(activityId,currentStatus);
    }

    // 审核通过
    public Result checkPass(String activityId, Enum<ActivityStatus> currentStatus){
        return stateMap.get(currentStatus).checkPass(activityId,currentStatus);
    }

    // 审核拒绝
    public Result checkRefuse(String activityId, Enum<ActivityStatus> currentStatus){
        return stateMap.get(currentStatus).checkRefuse(activityId,currentStatus);
    }

    // 撤审
    public Result checkRevoke(String activityId, Enum<ActivityStatus> currentStatus){
        return stateMap.get(currentStatus).checkRevoke(activityId,currentStatus);
    }

    // 活动关闭
    public Result close(String activityId, Enum<ActivityStatus> currentStatus){
        return stateMap.get(currentStatus).close(activityId,currentStatus);
    }

    // 活动开启
    public Result open(String activityId, Enum<ActivityStatus> currentStatus){
        return stateMap.get(currentStatus).open(activityId,currentStatus);
    }

    // 活动执行
    public Result doing(String activityId, Enum<ActivityStatus> currentStatus){
        return stateMap.get(currentStatus).doing(activityId,currentStatus);
    }

}
  • Client 客户端测试
/**
 * 客户端测试
 */
public class ActivityClient {
    public static void main(String[] args) {
        StateHandler stateHandler = new StateHandler();
        System.out.println(stateHandler.checkRefuse("1", ActivityStatus.Aduit));// 成功
        System.out.println(stateHandler.checkRefuse("1", ActivityStatus.Editing));// 非法操作
    }
}

1活动状态变更 from Aduit to Refuse
SUCCESS
ILLEGAL
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3