状态模式
概述
在状态模式中,我们用类来表示状态,State的意思就是状态。允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。
示例程序
如下图所示,这是一个提交审批的流程,例如钉钉的请假流程,编辑后走向提交,提交后可以撤回、通过、拒绝,只有当通过后才可以完成请假流程。
基础代码
流程信息
@Data
public class ActivityInfo {
private String activityId; // 流程ID
private String activityName; // 流程名称
private Enum<Status> status; // 流程状态
}
流程状态枚举
public enum Status {
// 1创建编辑、2提交、3审核通过(任务扫描成活动中)、4审核拒绝(可以撤审到编辑状态)、5活动中 、6 撤回
Editing, Commit, Pass, Refuse, Doing, Return
}
流程服务类
public class ActivityService {
private static Map<String, Enum<Status>> statusMap = new ConcurrentHashMap<String, Enum<Status>>();
public static void init(String activityId, Enum<Status> status) {
// 模拟查询活动信息
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.setActivityId(activityId);
activityInfo.setActivityName("请假流程");
activityInfo.setStatus(status);
statusMap.put(activityId, status);
}
/**
* 查询活动信息
*
* @param activityId 活动ID
* @return 查询结果
*/
public static ActivityInfo queryActivityInfo(String activityId) {
// 模拟查询活动信息
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.setActivityId(activityId);
activityInfo.setActivityName("请假流程");
activityInfo.setStatus(statusMap.get(activityId));
return activityInfo;
}
/**
* 查询活动状态
*
* @param activityId 活动ID
* @return 查询结果
*/
public static Enum<Status> queryActivityStatus(String activityId) {
return statusMap.get(activityId);
}
/**
* 执行状态变更
*
* @param activityId 活动ID
* @param beforeStatus 变更前状态
* @param afterStatus 变更后状态 b
*/
public static synchronized void execStatus(String activityId, Enum<Status> beforeStatus, Enum<Status> afterStatus) {
if (!beforeStatus.equals(statusMap.get(activityId))) return;
statusMap.put(activityId, afterStatus);
}
}
不使用状态模式
result返回类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private String code;
private String message;
}
控制层
只能使用if-else进行多次判断
public class ActivityExecStatusController {
/**
* 活动状态变更
* 1. 编辑中 -> 提交
* 2. 审核通过 -> 完成
* 3. 审核拒绝 -> 撤审、撤回
* 4. 提交 -> 通过、拒绝、撤回
* 5. 撤回 -> 编辑中
*
* @param activityId 活动ID
* @param beforeStatus 变更前状态
* @param afterStatus 变更后状态
* @return 返回结果
*/
public Result execStatus(String activityId, Enum<Status> beforeStatus, Enum<Status> afterStatus) {
// 1. 编辑中 -> 提交
if (Status.Editing.equals(beforeStatus)) {
if (Status.Commit.equals(afterStatus)) {
ActivityService.execStatus(activityId, beforeStatus, afterStatus);
return new Result("0000", "变更状态成功");
} else {
return new Result("0001", "变更状态拒绝");
}
}
// 2. 审核通过 -> 完成
if (Status.Pass.equals(beforeStatus)) {
if (Status.Doing.equals(afterStatus) ) {
ActivityService.execStatus(activityId, beforeStatus, afterStatus);
return new Result("0000", "变更状态成功");
} else {
return new Result("0001", "变更状态拒绝");
}
}
// 3. 审核拒绝 -> 撤审、撤回
if (Status.Refuse.equals(beforeStatus)) {
if (Status.Editing.equals(afterStatus) || Status.Return.equals(afterStatus)) {
ActivityService.execStatus(activityId, beforeStatus, afterStatus);
return new Result("0000", "变更状态成功");
} else {
return new Result("0001", "变更状态拒绝");
}
}
// 4. 提交 -> 通过、拒绝、撤回
if (Status.Commit.equals(beforeStatus)) {
if (Status.Pass.equals(afterStatus) || Status.Refuse.equals(afterStatus) || Status.Return.equals(afterStatus)) {
ActivityService.execStatus(activityId, beforeStatus, afterStatus);
return new Result("0000", "变更状态成功");
} else {
return new Result("0001", "变更状态拒绝");
}
}
// 5. 撤回 -> 编辑中
if (Status.Return.equals(beforeStatus)){
if (Status.Editing.equals(afterStatus)){
ActivityService.execStatus(activityId, beforeStatus, afterStatus);
return new Result("0000", "变更状态成功");
} else {
return new Result("0001", "变更状态拒绝");
}
}
return new Result("0001", "非可处理的活动状态变更");
}
}
测试
从编辑状态直接变更到审核拒绝状态是走不通的;从编辑状态变更到提交审核ok
@Slf4j
@SpringBootTest
class Practice2201ApplicationTests {
@Test
void contextLoads() {
// 初始化数据
String activityId = "100001";
ActivityService.init(activityId, Status.Editing);
ActivityExecStatusController activityExecStatusController = new ActivityExecStatusController();
Result resultRefuse = activityExecStatusController.execStatus(activityId, Status.Editing, Status.Refuse);
log.info("测试结果(编辑中To审核拒绝):{}", resultRefuse);
Result resultCheck = activityExecStatusController.execStatus(activityId, Status.Editing, Status.Commit);
log.info("测试结果(编辑中To提交审核):{}", resultCheck);
}
}
//结果
2022-06-27 19:47:47.178 INFO 21384 --- [ main] c.y.p.Practice2201ApplicationTests : 测试结果(编辑中To审核拒绝):Result(code=0001, message=变更状态拒绝)
2022-06-27 19:47:47.180 INFO 21384 --- [ main] c.y.p.Practice2201ApplicationTests : 测试结果(编辑中To提交审核):Result(code=0000, message=变更状态成功)
状态模式实现
State抽象类
State抽象类在整个接口中提供了各项状态流转服务的接口
public abstract class State {
/**
* 编辑
*
* @param activityId
* @param currentStatus
* @return
*/
public abstract Result checkEdit(String activityId, Enum<Status> currentStatus);
/**
* 提交
*
* @param activityId
* @param currentStatus
* @return 执行结果
*/
public abstract Result checkCommit(String activityId, Enum<Status> currentStatus);
/**
* 审核通过
*
* @param activityId
* @param currentStatus
* @return 执行结果
*/
public abstract Result checkPass(String activityId, Enum<Status> currentStatus);
/**
* 审核拒绝
*
* @param activityId
* @param currentStatus
* @return 执行结果
*/
public abstract Result checkRefuse(String activityId, Enum<Status> currentStatus);
/**
* 撤销
*
* @param activityId
* @param currentStatus
* @return 执行结果
*/
public abstract Result checkReturn(String activityId, Enum<Status> currentStatus);
/**
* 完成
*
* @param activityId
* @param currentStatus
* @return 执行结果
*/
public abstract Result doing(String activityId, Enum<Status> currentStatus);
}
具体的实现类
继承了State,实际开发过程中,需要写出所有状态的实现类,现在我们以提交状态举例
public class CommitState extends State {
@Override
public Result checkEdit(String activityId, Enum<Status> currentStatus) {
return new Result("0001", "提交后不可编辑");
}
@Override
public Result checkCommit(String activityId, Enum<Status> currentStatus) {
return new Result("0001","不可重复提交");
}
@Override
public Result checkPass(String activityId, Enum<Status> currentStatus) {
return new Result("0000","审批通过");
}
@Override
public Result checkRefuse(String activityId, Enum<Status> currentStatus) {
return new Result("0000","审批拒绝");
}
@Override
public Result checkReturn(String activityId, Enum<Status> currentStatus) {
return new Result("0000","撤回申请");
}
@Override
public Result doing(String activityId, Enum<Status> currentStatus) {
return new Result("0001", "审批未通过");
}
}
状态控制器
这是对状态服务的统一控制中心,可以看到在构造函数中提供了所有状态和实现的具体关联,放到Map数据结构中。
所有变更状态的参数都一样,第一个为流程ID,第二个为当前状态
public class StateHandler {
private Map<Enum<Status>, State> stateMap = new ConcurrentHashMap<Enum<Status>, State>();
public StateHandler() {
stateMap.put(Status.Commit, new CommitState()); // 提交
stateMap.put(Status.Doing, new DoingState()); // 活动中
stateMap.put(Status.Editing, new EditingState()); // 编辑中
stateMap.put(Status.Pass, new PassState()); // 审核通过
stateMap.put(Status.Refuse, new RefuseState()); // 审核拒绝
stateMap.put(Status.Return, new ReturnState()); // 审核拒绝
}
public Result checkCommit(String activityId, Enum<Status> currentStatus) {
return stateMap.get(currentStatus).checkCommit(activityId, currentStatus);
}
public Result checkPass(String activityId, Enum<Status> currentStatus) {
return stateMap.get(currentStatus).checkPass(activityId, currentStatus);
}
public Result checkRefuse(String activityId, Enum<Status> currentStatus) {
return stateMap.get(currentStatus).checkRefuse(activityId, currentStatus);
}
public Result checkReturn(String activityId, Enum<Status> currentStatus) {
return stateMap.get(currentStatus).checkReturn(activityId, currentStatus);
}
public Result doing(String activityId, Enum<Status> currentStatus) {
return stateMap.get(currentStatus).doing(activityId, currentStatus);
}
}
测试
现在需要将提交状态修改为通过状态
@Slf4j
@SpringBootTest
class Practice2202ApplicationTests {
@Test
void contextLoads() {
String activityId = "100001";
ActivityService.init(activityId, Status.Commit);
StateHandler stateHandler = new StateHandler();
Result result = stateHandler.checkPass(activityId, Status.Commit);
log.info("测试结果(编辑中To提审活动):{}", result);
log.info("活动信息:{} 状态:{}", ActivityService.queryActivityInfo(activityId), ActivityService.queryActivityInfo(activityId).getStatus());
}
}
//结果
2022-06-27 20:09:50.616 INFO 30375 --- [ main] c.y.p.Practice2202ApplicationTests : 测试结果(编辑中To提审活动):Result(code=0000, message=审批通过)
2022-06-27 20:09:50.619 INFO 30375 --- [ main] c.y.p.Practice2202ApplicationTests : 活动信息:ActivityInfo(activityId=100001, activityName=请假流程, status=Commit) 状态:Commit
总结
优点:
- 封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
- 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以让我们避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点:
- 状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度。
- 状态模式对“开闭原则”的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。
使用场景:
- 对象的行为依赖于它的状态(如某些属性值),状态的改变将导致行为的变化。
- 在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强。