【设计模式】行为型-⑤状态模式
【设计模式】行为型-⑤状态模式
注
学习核心
- 状态模式核心
- 概念:状态模式描述的是一个行为下的多种状态变更
- 组成:
- 状态枚举(
Enum<Status>):定义业务流程中涉及到的状态 - 抽象状态类(
State):定义状态流转的操作方法- 对于一些场景,如果状态流转操作方法类似,也会考虑只定义一个方法进行简化,然后各自子类业务逻辑进行丰富,此处主要是让职责更清晰,所以拆分了多个流转方法
- 具体状态类(
xxxState):继承抽象状态类,实现当前状态对应操作方法的流转逻辑(如果不涉及当前状态操作则可忽略不实现或者提示操作非法) - 状态服务类(
StateHandler):提供统一的状态流转服务管理,维护了状态枚举和具体状态类的关联,通过其提供的方法入口调用相应的流转方法
- 状态枚举(
- 应用场景分析
- 【BPM流程】审核状态流转场景:营销活动上线的状态流转
概念核心
状态模式:描述的是一个行为下的多种状态变更。它是以状态为作为处理核心,拆解每个状态的出和入,据此来进行状态流转
实现核心:
- 拆解流程中涉及的状态(梳理状态、梳理操作(控制状态流转))
- 构建抽象状态类,定义状态流转方法规范
- 构建具体状态类,填充状态流转逻辑
- 构建状态服务管理类,定义统一状态流转的操作入口
1.BPM流程分析
以一个简单的BPM流程进行分析,理解状态模式的设计思路。
- 状态分析:1-拟稿、2-审核、3-审核成功、4-审核失败、5-完结
- 流转分析:提交(1->2)、审核通过(2->3)、审核拒绝(2->4)、归档(3->5、4->5)
/**
* 状态模式:状态流转
*/
public class StateDemo {
public static void main(String[] args) {
StateHandler handler = new StateHandler();
handler.commit(2, 3);
handler.commit(1, 2);
handler.pass(1, 2);
handler.pass(2, 3);
}
}
/**
* 状态梳理:
* 1 拟稿
* 2 审核
* 3 审核成功
* 4 审核失败
* 5 完结(归档)
* 基于上述分析将状态流转的过程划分为几个状态之间的流转,每个操作即每个状态执行的操作
* 但并不是每个状态都可以执行所有的操作,需对前置状态进行校验(例如只能从审核态=>审核成功或审核失败,而不能由其他状态转过来)
* 所谓操作,即触发状态流转的方法定义,可以有如下设计
* - 提交(1->2)
* - 审核通过(2->3)
* - 审核拒绝(2->4)
* - 归档(3->5、4->5)
*/
// ① 定义状态(抽象状态)
interface State {
// 流转方法定义
public void commit(int prevState, int nextState); // 提交
public void pass(int prevState, int nextState); // 审核通过
public void reject(int prevState, int nextState); // 审核拒绝
public void finish(int prevState, int nextState); // 归档
}
// ② 具体状态子类
class DraftState implements State {
@Override
public void commit(int prevState, int nextState) {
System.out.println("状态由" + prevState + "变更为" + nextState);
}
@Override
public void pass(int prevState, int nextState) {
System.out.println("非法操作");
}
@Override
public void reject(int prevState, int nextState) {
System.out.println("非法操作");
}
@Override
public void finish(int prevState, int nextState) {
System.out.println("非法操作");
}
}
class AduitState implements State {
@Override
public void commit(int prevState, int nextState) {
System.out.println("非法操作");
}
@Override
public void pass(int prevState, int nextState) {
System.out.println("审核通过");
}
@Override
public void reject(int prevState, int nextState) {
System.out.println("审核拒绝");
}
@Override
public void finish(int prevState, int nextState) {
System.out.println("非法操作");
}
}
// ③ 定义状态控制类
class StateHandler {
// 构建状态映射
static Map<Integer, State> map = new HashMap<>();
static {
map.put(1, new DraftState());
map.put(2, new AduitState());
// map.put(1,new AduitState()); ......
}
// 定义方法执行操作
public void commit(int prevState, int nextState) {
map.get(prevState).commit(prevState, nextState);
}
public void pass(int prevState, int nextState) {
map.get(prevState).pass(prevState, nextState);
}
}
上述是基于接口设计,实际上如果将抽象状态设计为抽象类的结构会更好,从上面的代码分析可知,实际上每个状态真正可执行流转的方法可能就只有几个,如果是基于接口设计的模式,就必须要实现所有接口方法,那么对于一些极少状态的状态子类来说实现上并不友好,因此更多考虑的是用抽象类,定义抽象方法、默认的实现,只有具体子类需要扩展功能的时候才去重写方法
/**
* 状态模式:状态流转
*/
public class StateDemo2 {
public static void main(String[] args) {
StateHandler handler = new StateHandler();
handler.commit(2, 3);
handler.commit(1, 2);
handler.pass(1, 2);
handler.pass(2, 3);
}
}
/**
* 状态梳理:
* 1 拟稿
* 2 审核
* 3 审核成功
* 4 审核失败
* 5 完结(归档)
* 基于上述分析将状态流转的过程划分为几个状态之间的流转,每个操作即每个状态执行的操作
* 但并不是每个状态都可以执行所有的操作,需对前置状态进行校验(例如只能从审核态=>审核成功或审核失败,而不能由其他状态转过来)
* 所谓操作,即触发状态流转的方法定义,可以有如下设计
* - 提交(1->2)
* - 审核通过(2->3)
* - 审核拒绝(2->4)
* - 归档(3->5、4->5)
*/
// ① 定义状态(抽象状态)
abstract class State {
// 流转方法定义(默认设置一个操作处理结果状态,由具体子类来进行优化)
// 提交
public void commit(int prevState, int nextState) {
System.out.println("非法操作");
}
// 审核通过
public void pass(int prevState, int nextState) {
System.out.println("非法操作");
}
// 审核拒绝
public void reject(int prevState, int nextState) {
System.out.println("非法操作");
}
// 归档
public void finish(int prevState, int nextState) {
System.out.println("非法操作");
}
}
// ② 具体状态子类
class DraftState extends State {
@Override
public void commit(int prevState, int nextState) {
System.out.println("状态由" + prevState + "变更为" + nextState);
}
}
class AduitState extends State {
@Override
public void pass(int prevState, int nextState) {
System.out.println("审核通过");
}
@Override
public void reject(int prevState, int nextState) {
System.out.println("审核拒绝");
}
}
// ③ 定义状态控制类
class StateHandler {
// 构建状态映射
static Map<Integer, State> map = new HashMap<>();
static {
map.put(1, new DraftState());
map.put(2, new AduitState());
// map.put(1,new AduitState()); ......
}
// 定义方法执行操作
public void commit(int prevState, int nextState) {
map.get(prevState).commit(prevState, nextState);
}
public void pass(int prevState, int nextState) {
map.get(prevState).pass(prevState, nextState);
}
}
场景案例分析
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
