状态模式介绍
状态模式描述的是一个行为下的多种状态变更,比如我们最常见的一个网站的页面,在你登录与不登录下展示的内容是略有差异的(不登录不能展示个人信息),而这种登录与不登录就是我们通过改变状态,而让整个行为发生了变化。
至少80后、90后的小伙伴基本都用过这种磁带放音机(可能没有这个好看),它的上面是一排按钮,当放入磁带后,通过上面的按钮就可以让放音机播放磁带上的内容(listen to 英语听力考试),而且有些按钮是互斥的,当在某个状态下才可以按另外的按钮(这在设计模式里也是一个关键的点)。
案例场景模拟
在本案例中我们模拟营销活动审核状态流转场景(一个活动的上线是多个层级审核上线的)
在上图中也可以看到我们的流程节点中包括了各个状态到下一个状态扭转的关联条件,比如;审核通过才能到活动中,而不能从编辑中直接到活动中,而这些状态的转变就是我们要完成的场景处理。
大部分程序员基本都开发过类似的业务场景,需要对活动或者一些配置需要审核后才能对外发布,而这个审核的过程往往会随着系统的重要程度而设立多级控制,来保证一个活动可以安全上线,避免造成资损。
当然有时候会用到一些审批流的过程配置,也是非常方便开发类似的流程的,也可以在配置中设定某个节点的审批人员。但这不是我们主要体现的点,在本案例中我们主要是模拟学习对一个活动的多个状态节点的审核控制。
状态模式实现代码
1. 工程结构
java
└── org.itstack.demo.design
├── event
│ ├── CheckState.java
│ └── CloseState.java
│ └── DoingState.java
│ └── EditingState.java
│ └── OpenState.java
│ └── PassState.java
│ └── RefuseState.java
├── Result.java
├── State.java
└── StateHandler.java
状态模式模型结构
以上是状态模式的整个工程结构模型,State是一个抽象类,定义了各种操作接口(提审、审核、拒审等)。
右侧的不同颜色状态与我们场景模拟中的颜色保持一致,是各种状态流程流转的实现操作。这里的实现有一个关键点就是每一种状态到下一个状态,都分配到各个实现方法中控制,也就不需要if语言进行判断了。
最后是StateHandler对状态流程的统一处理,里面提供Map结构的各项服务接口调用,也就避免了使用if判断各项状态转变的流程
2. 代码实现
2.1 定义状态抽象类
public abstract class State {
/**
* 活动提审
*
* @param activityId 活动ID
* @param currentStatus 当前状态
* @return 执行结果
*/
public abstract Result arraignment(String activityId, Enum<Status> currentStatus);
/**
* 审核通过
*
* @param activityId 活动ID
* @param currentStatus 当前状态
* @return 执行结果
*/
public abstract Result checkPass(String activityId, Enum<Status> currentStatus);
/**
* 审核拒绝
*
* @param activityId 活动ID
* @param currentStatus 当前状态
* @return 执行结果
*/
public abstract Result checkRefuse(String activityId, Enum<Status> currentStatus);
/**
* 撤审撤销
*
* @param activityId 活动ID
* @param currentStatus 当前状态
* @return 执行结果
*/
public abstract Result checkRevoke(String activityId, Enum<Status> currentStatus);
/**
* 活动关闭
*
* @param activityId 活动ID
* @param currentStatus 当前状态
* @return 执行结果
*/
public abstract Result close(String activityId, Enum<Status> currentStatus);
/**
* 活动开启
*
* @param activityId 活动ID
* @param currentStatus 当前状态
* @return 执行结果
*/
public abstract Result open(String activityId, Enum<Status> currentStatus);
/**
* 活动执行
*
* @param activityId 活动ID
* @param currentStatus 当前状态
* @return 执行结果
*/
public abstract Result doing(String activityId, Enum<Status> currentStatus);
}
在整个接口中提供了各项状态流转服务的接口,例如;活动提审、审核通过、审核拒绝、撤审撤销等7个方法。
在这些方法中所有的入参都是一样的,activityId(活动ID)、currentStatus(当前状态),只有他们的具体实现是不同的。
2.2 部分状态流转实现
编辑
public class EditingState extends State {
public Result arraignment(String activityId, Enum<Status> currentStatus) {
ActivityService.execStatus(activityId, currentStatus, Status.Check);
return new Result("0000", "活动提审成功");
}
public Result checkPass(String activityId, Enum<Status> currentStatus) {
return new Result("0001", "编辑中不可审核通过");
}
public Result checkRefuse(String activityId, Enum<Status> currentStatus) {
return new Result("0001", "编辑中不可审核拒绝");
}
@Override
public Result checkRevoke(String activityId, Enum<Status> currentStatus) {
return new Result("0001", "编辑中不可撤销审核");
}
public Result close(String activityId, Enum<Status> currentStatus) {
ActivityService.execStatus(activityId, currentStatus, Status.Close);
return new Result("0000", "活动关闭成功");
}
public Result open(String activityId, Enum<Status> currentStatus) {
return new Result("0001", "非关闭活动不可开启");
}
public Result doing(String activityId, Enum<Status> currentStatus) {
return new Result("0001", "编辑中活动不可执行活动中变更");
}
}
提审
public class CheckState extends State {
public Result arraignment(String activityId, Enum<Status> currentStatus) {
return new Result("0001", "待审核状态不可重复提审");
}
public Result checkPass(String activityId, Enum<Status> currentStatus) {
ActivityService.execStatus(activityId, currentStatus, Status.Pass);
return new Result("0000", "活动审核通过完成");
}
public Result checkRefuse(String activityId, Enum<Status> currentStatus) {
ActivityService.execStatus(activityId, currentStatus, Status.Refuse);
return new Result("0000", "活动审核拒绝完成");
}
@Override
public Result checkRevoke(String activityId, Enum<Status> currentStatus) {
ActivityService.execStatus(activityId, currentStatus, Status.Editing);
return new Result("0000", "活动审核撤销回到编辑中");
}
public Result close(String activityId, Enum<Status> currentStatus) {
ActivityService.execStatus(activityId, currentStatus, Status.Close);
return new Result("0000", "活动审核关闭完成");
}
public Result open(String activityId, Enum<Status> currentStatus) {
return new Result("0001", "非关闭活动不可开启");
}
public Result doing(String activityId, Enum<Status> currentStatus) {
return new Result("0001", "待审核活动不可执行活动中变更");
}
}
这里提供了两个具体实现类的内容,编辑状态和提审状态。
例如在这两个实现类中,checkRefuse这个方法对于不同的类中有不同的实现,也就是不同状态下能做的下一步流转操作已经可以在每一个方法中具体控制了。
其他5个类的操作是类似的具体就不在这里演示了,大部分都是重复代码。可以通过源码进行学习理解。
2.3 状态处理服务
public class StateHandler {
private Map<Enum<Status>, State> stateMap = new ConcurrentHashMap<Enum<Status>, State>();
public StateHandler() {
stateMap.put(Status.Check, new CheckState()); // 待审核
stateMap.put(Status.Close, new CloseState()); // 已关闭
stateMap.put(Status.Doing, new DoingState()); // 活动中
stateMap.put(Status.Editing, new EditingState()); // 编辑中
stateMap.put(Status.Open, new OpenState()); // 已开启
stateMap.put(Status.Pass, new PassState()); // 审核通过
stateMap.put(Status.Refuse, new RefuseState()); // 审核拒绝
}
public Result arraignment(String activityId, Enum<Status> currentStatus) {
return stateMap.get(currentStatus).arraignment(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 checkRevoke(String activityId, Enum<Status> currentStatus) {
return stateMap.get(currentStatus).checkRevoke(activityId, currentStatus);
}
public Result close(String activityId, Enum<Status> currentStatus) {
return stateMap.get(currentStatus).close(activityId, currentStatus);
}
public Result open(String activityId, Enum<Status> currentStatus) {
return stateMap.get(currentStatus).open(activityId, currentStatus);
}
public Result doing(String activityId, Enum<Status> currentStatus) {
return stateMap.get(currentStatus).doing(activityId, currentStatus);
}
}
这是对状态服务的统一控制中心,可以看到在构造函数中提供了所有状态和实现的具体关联,放到Map数据结构中。
同时提供了不同名称的接口操作类,让外部调用方可以更加容易的使用此项功能接口
3. 测试验证
3.1 编写测试类1
//编辑中状态改为活动提审
@Test
public void test_Editing2Arraignment() {
String activityId = "100001";
ActivityService.init(activityId, Status.Editing);
StateHandler stateHandler = new StateHandler();
Result result = stateHandler.arraignment(activityId, Status.Editing);
log.info("测试结果(编辑中To提审活动):{}", JSON.toJSONString(result));
log.info("活动信息:{} 状态:{}", JSON.toJSONString(ActivityService.queryActivityInfo(activityId)), JSON.toJSONString(ActivityService.queryActivityInfo(activityId).getStatus()));
}
测试结果
15:46:01.726 [main] INFO cn.lasse.design.ApiTest - 测试结果(编辑中To提审活动):{"code":"0000","info":"活动提审成功"}
15:46:01.752 [main] INFO cn.lasse.design.ApiTest - 活动信息:{"activityId":"100001","activityName":"早起学习打卡领奖活动","beginTime":1673768761734,"endTime":1673768761734,"status":"Check"} 状态:"Check"
3.2 编写测试类2
//编辑中状态改为已开启
@Test
public void test_Editing2Open() {
String activityId = "100001";
ActivityService.init(activityId, Status.Editing);
StateHandler stateHandler = new StateHandler();
Result result = stateHandler.open(activityId, Status.Editing);
log.info("测试结果(编辑中To开启活动):{}", JSON.toJSONString(result));
log.info("活动信息:{} 状态:{}", JSON.toJSONString(ActivityService.queryActivityInfo(activityId)), JSON.toJSONString(ActivityService.queryActivityInfo(activityId).getStatus()));
}
测试结果
15:57:42.166 [main] INFO cn.lasse.design.ApiTest - 测试结果(编辑中To开启活动):{"code":"0001","info":"非关闭活动不可开启"}
15:57:42.189 [main] INFO cn.lasse.design.ApiTest - 活动信息:{"activityId":"100001","activityName":"早起学习打卡领奖活动","beginTime":1673769462177,"endTime":1673769462177,"status":"Editing"} 状态:"Editing"
3.2 编写测试类3
@Test
public void test_Refuse2Doing() {
String activityId = "100001";
ActivityService.init(activityId, Status.Refuse);
StateHandler stateHandler = new StateHandler();
Result result = stateHandler.doing(activityId, Status.Refuse);
log.info("测试结果(拒绝To活动中):{}", JSON.toJSONString(result));
log.info("活动信息:{} 状态:{}", JSON.toJSONString(ActivityService.queryActivityInfo(activityId)), JSON.toJSONString(ActivityService.queryActivityInfo(activityId).getStatus()));
}
测试结果
16:00:12.819 [main] INFO cn.lasse.design.ApiTest - 测试结果(拒绝To活动中):{"code":"0001","info":"审核拒绝不可执行活动为进行中"}
16:00:12.844 [main] INFO cn.lasse.design.ApiTest - 活动信息:{"activityId":"100001","activityName":"早起学习打卡领奖活动","beginTime":1673769612830,"endTime":1673769612830,"status":"Refuse"} 状态:"Refuse"
总结
使用设计模式代码的结构也更加清晰易于扩展。这就是设计模式的好处,可以非常强大的改变原有代码的结构,让以后的扩展和维护都变得容易些。
在实现结构的编码方式上可以看到这不再是面向过程的编程,而是面向对象的结构。并且这样的设计模式满足了单一职责和开闭原则,当你只有满足这样的结构下才会发现代码的扩展是容易的,也就是增加和修改功能不会影响整体的变化。
但如果状态和各项流转较多像本文的案例中,就会产生较多的实现类。因此可能也会让代码的实现上带来了时间成本,因为如果遇到这样的场景可以按需评估投入回报率。主要点在于看是否经常修改、是否可以做成组件化、抽离业务与非业务功能。