【设计模式】行为型-⑤状态模式
【设计模式】行为型-⑤状态模式
注
学习核心
- 状态模式核心
- 概念:状态模式描述的是一个行为下的多种状态变更
- 组成:
- 状态枚举(
Enum<Status>
):定义业务流程中涉及到的状态 - 抽象状态类(
State
):定义状态流转的操作方法- 对于一些场景,如果状态流转操作方法类似,也会考虑只定义一个方法进行简化,然后各自子类业务逻辑进行丰富,此处主要是让职责更清晰,所以拆分了多个流转方法
- 具体状态类(
xxxState
):继承抽象状态类,实现当前状态对应操作方法的流转逻辑(如果不涉及当前状态操作则可忽略不实现或者提示操作非法) - 状态服务类(
StateHandler
):提供统一的状态流转服务管理,维护了状态枚举和具体状态类的关联,通过其提供的方法入口调用相应的流转方法
- 状态枚举(
- 应用场景分析
- 【BPM流程】审核状态流转场景:营销活动上线的状态流转
概念核心
状态模式:描述的是一个行为下的多种状态变更。它是以状态为作为处理核心,拆解每个状态的出和入,据此来进行状态流转
实现核心:
- 拆解流程中涉及的的状态
- 构建抽象状态类,定义状态流转方法规范
- 构建具体状态类,填充状态流转逻辑
- 构建状态服务管理类,定义统一状态流转的操作入口
场景案例分析
1.【营销活动】审核状态流转场景
核心:营销活动、多级审核、状态流转、流程控制
【营销活动】审核状态流转场景:一个活动的上线是需要多个层级审核上线的。流程节点中包括了各个状态到下个状态扭转的关联条件,此处的场景处理就是要完成这些状态的转变。
审核类的业务场景是一个比较常见的开发场景,当对活动或者配置进行修改后需要审核通过才能对外发布,⽽这 个审核的过程往往会随着系统的重要程度⽽设⽴多级控制,来保证⼀个活动可以安全上线,避免造成资源损耗。 一些场景中会用到审批流的过程配置,也是⾮常⽅便开发类似的流程的,也可以在配置中设定某个 节点的审批⼈员。但此处主要体现的点在于模拟学习对⼀个活动/任务/流程的多个状态节点的审核控制
🎃活动状态流转分析(状态变更)
针对流程管理类相关设计,可能涉及很多记录状态的流转、变更,一开始分析可能会有点懵,包括自己在一开始接触这种状态流转概念的时候,经常会被每个状态可能是由什么状态转过来的、又可以转变为什么状态搞的晕头转向,但接触过流程管理相关系统的开发,了解相关流程引擎的工作原理和思路,在针对一些流程类业务开发的时候,要先抓住业务流程类开发的重点是“流转处理”,而记录的流转则是由状态节点一个个串联起来的,因此在梳理流程状态的时候可以尝试以下思路(以lottery活动流转为例进行说明)
1.先梳理业务流程,然后分析涉及的流程状态节点
查看、编辑、提审、撤审、通过、拒绝、关闭、开启、执行
2.分析流程状态节点可以执行的操作(当前的节点状态的流程可以执行的下一步操作是什么(可以变更的target状态是什么))
此处不要将"当前状态节点可能是由什么状态转过来"纳入分析,因为这会让自己处于混沌状态,也是流程开发的一个小误区。当确定了流程开发的步骤(业务流程),其相应的节点状态也确定下来,因此只需要根据流程节点状态的流转走便能形成"回路",将重点侧重于"当前状态可以执行什么操作、变成下一个targetStatus"
反过来想,之所以一开始会考虑某个节点状态是由什么转过来的,也是基于业务校验的一个考虑,担心存在不符合流转规则的数据,但如果能够在流转的过程中去控制(”校验流转状态变更,从而限制入口“),这个问题也就不复存在(流转规则制定、流转过程校验)
而流转规则的指定则需结合实际业务考虑,例如针对一些复杂的业务,某个节点状态又可根据不同的情况限制相应的操作
此处则需区分“状态流转”和“业务功能限制”,“状态流转”只需考虑当前节点能否流转到下一节点,而“业务功能限制”则需考虑当前节点的上一节点是什么,可以执行什么操作(可限制功能访问甚至是限制下一节点的流转),简单举例说明
3.根据每个流转状态节点,制定相应的方法供状态变更
以营销活动的审核流程场景为例,说明如下所示:依据流程分析每个状态节点的状态和流转,只考虑当前节点状态的出入分析。针对某个状态节点只考虑“出”的情况,即由当前节点和可以执行什么操作(变更为指定状态),“入”的情况则可在其他状态中体现
- 编辑态
- 提审态
- 撤审态
- 通过态
- 拒绝态
- 关闭态
- 开启态
- 执行态
✨传统实现方式
针对状态流转场景,最基础的实现方式就是梳理状态流转的关系,通过设定流程状态,根据当前流程状态判断当前节点操作人员可执行的操作
参考代码实现
- 活动信息实体定义
/**
* 活动信息
*/
@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