文章目录
- 一、定义:状态模式
- 二、模拟场景:状态模式
- 2.1 状态模式
- 2.2 引入依赖
- 2.3 工程结构
- 2.4 模拟审核状态流转
- 2.4.1 活动状态枚举
- 2.4.2 活动信息类
- 2.4.3 活动服务接口
- 2.4.4 返回结果类
- 三、违背方案:状态模式
- 3.0 引入依赖
- 3.1 工程结构
- 3.2 活动执行状态变更控制层
- 3.3 单元测试
- 四、改善代码:状态模式
- 4.0 引入依赖
- 4.1 工程结构
- 4.2 状态模式结构图
- 4.3 活动状态变更流程
- 4.3.1 定义状态抽象类
- 4.3.2 待审核状态流转实现
- 4.3.3 活动关闭状态流转实现
- 4.3.4 活动中状态流转实现
- 4.3.5 编辑中状态流转实现
- 4.3.6 活动开启状态流转实现
- 4.3.7 审核通过状态流转实现
- 4.3.8 审核拒绝状态流转实现
- 4.3.9 活动执行者
- 4.4 单元测试
- 4.4.1 编辑中到提审活动测试验证
- 4.4.2 编辑中到开启活动测试验证
- 4.4.3 审批拒绝到活动中测试验证
- 4.4.4 审批拒绝到撤审测试验证
- 五、总结:状态模式
一、定义:状态模式
- **状态模式:**是一个行为下的多种状态变更。
- 比如我们常见的一个网站的页面,在你登录与不登录下展示的内容是略有差异的 (不登录不能展示个人信息)。
- 这种
登录
与不登录
就是我们通过改变 状态,而让整个行为发生了变化。
- 状态模式的使用场景。例如:磁带放音机。
- 它的上面是一排按钮,当放入磁带后,通过上面的按钮就可以让放音机播放磁带上的内容。
- 而且有些按钮是互斥的,当在某个状态下才可以按另外的按钮(
这在设计模式里也是一个关键的点
)。
二、模拟场景:状态模式
2.1 状态模式
- 模拟营销活动审核状态流转场景(一个活动的上线是多个层级审核上线的)。
- 在上图中可以看到我们的流程节点中包括了各个状态到下一个状态扭转的关联条件。
- 比如:审核通过才能到活动中,而不能从编辑中直接到活动中,而这些状态的转变就是我们要完成的场景处理。
- 我们都开发过类似的业务场景,需要对活动或者一些配置需要审核后才能对外发布,而这个审核的过程往往会随着系统的重要程序而设立多级控制,来保证一个活动可以安全上线,避免造成资损。
- 当然有时候会用到一些审批流的过程配置,也是非常方便开发类似的流程的,也可以在配置中设定某个节点的审批人员。
- 在本案例中们我主要是模拟学习对一个活动的多个状态节点的审核控制。
2.2 引入依赖
pom.xml
<dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.62</version></dependency><!-- LOGGING begin --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.5</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>jcl-over-slf4j</artifactId><version>1.7.5</version></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.0.9</version><exclusions><exclusion><artifactId>slf4j-api</artifactId><groupId>org.slf4j</groupId></exclusion></exclusions></dependency>
</dependencies>
2.3 工程结构
design-20.0-0
|——src|——main|--java|--com.lino.design|-ActicityInfo.java|-ActivityService.java|-Result.java|-Status.java
2.4 模拟审核状态流转
2.4.1 活动状态枚举
Status.java
package com.lino.design;/*** @description: 状态枚举*/
public enum Status {/*** 1.创建编辑,2.待审核,3.审核通过(任务扫描成活动中),* 4.审核拒绝(可以撤审到编辑状态),5.活动中,* 6.活动关闭,7.活动开启(任务扫描成活动中)*/Editing, Check, Pass, Refuse, Doing, Close, Open
}
2.4.2 活动信息类
ActicityInfo.java
package com.lino.design;import java.util.Date;
import java.util.Map;/*** @description: 活动类*/
public class ActicityInfo {/*** 活动ID*/private String acticityId;/*** 活动名称*/private String acticityName;/*** 活动状态*/private Enum<Status> status;/*** 开始时间*/private Date beginTime;/*** 结束时间*/private Date endTime;public String getActicityId() {return acticityId;}public void setActicityId(String acticityId) {this.acticityId = acticityId;}public String getActicityName() {return acticityName;}public void setActicityName(String acticityName) {this.acticityName = acticityName;}public Enum<Status> getStatus() {return status;}public void setStatus(Enum<Status> status) {this.status = status;}public Date getBeginTime() {return beginTime;}public void setBeginTime(Date beginTime) {this.beginTime = beginTime;}public Date getEndTime() {return endTime;}public void setEndTime(Date endTime) {this.endTime = endTime;}
}
- 基本的活动信息:活动
ID
、活动名称、活动状态、开始时间、结束时间
2.4.3 活动服务接口
ActivityService.java
package com.lino.design;import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @description: 活动服务类*/
public class ActivityService {private static Map<String, Enum<Status>> statusMap = new ConcurrentHashMap<>();/*** 初始化活动** @param activityId 活动ID* @param status 活动状态*/public static void init(String activityId, Enum<Status> status) {// 模拟查询活动信息ActicityInfo acticityInfo = new ActicityInfo();acticityInfo.setActicityId(activityId);acticityInfo.setActicityName("早起学习打卡领奖活动");acticityInfo.setStatus(status);acticityInfo.setBeginTime(new Date());acticityInfo.setEndTime(new Date());statusMap.put(activityId, status);}/*** 查询活动信息** @param activityId 活动ID* @return 活动信息*/public static ActicityInfo queryActivityInfo(String activityId) {// 模拟查询活动信息ActicityInfo acticityInfo = new ActicityInfo();acticityInfo.setActicityId(activityId);acticityInfo.setActicityName("早起学习打卡领奖活动");acticityInfo.setStatus(statusMap.get(activityId));acticityInfo.setBeginTime(new Date());acticityInfo.setEndTime(new Date());return acticityInfo;}/*** 查询活动状态** @param activityId 活动ID* @return 活动状态*/public static Enum<Status> queryActivityStatus(String activityId) {return statusMap.get(activityId);}/*** 执行状态变更** @param activityId 活动ID* @param beforeStatus 变更前状态* @param afterStatus 变更后状态*/public static synchronized void execStatus(String activityId, Enum<Status> beforeStatus, Enum<Status> afterStatus) {if (!beforeStatus.equals(statusMap.get(activityId))) {return;}statusMap.put(activityId, afterStatus);}
}
- 在这个静态类中提供了活动的查询和状态变更接口。
queryActivityInfo
:查询活动信息。queryActivityStatus
:查询活动状态。execStatus
:执行状态变更。
- 同时使用
Map
的结构来记录活动ID
和状态变化信息,另外还有init
方法来初始化活动数据。- 实际的开发中这类信息基本都是从
数据库
或者Redis
中获取。
- 实际的开发中这类信息基本都是从
2.4.4 返回结果类
Result.java
package com.lino.design;/*** @description: 结果类*/
public class Result {/*** 编码*/private String code;/*** 描述*/private String info;public Result(String code, String info) {this.code = code;this.info = info;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getInfo() {return info;}public void setInfo(String info) {this.info = info;}
}
三、违背方案:状态模式
对于这样各种状态的变更,最让我们直接想到的就是使用
if
和else
进行判断处理。
每一个状态可以流转到下一个什么状态,都可以使用嵌套的if
实现。
3.0 引入依赖
<dependencies><dependency><groupId>com.lino</groupId><artifactId>design-20.0-0</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>
3.1 工程结构
design-20.0-1
|——src|——main|--java|--com.lino.design|-ActivityExecStatusController.java|--test|--com.lino.design.test|-ApiTest.java
3.2 活动执行状态变更控制层
ActivityExecStatusController.java
package com.lino.design;/*** @description: 活动状态变更控制层*/
public class ActivityExecStatusController {/*** 活动状态变更* 1. 编辑中 -> 提审、关闭* 2. 审核通过 -> 拒绝、关闭、活动中* 3. 审核拒绝 -> 撤审、关闭* 4. 活动中 -> 关闭* 5. 活动关闭 -> 开启* 6. 活动开启 -> 关闭** @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.Check.equals(afterStatus) || Status.Close.equals(afterStatus)) {ActivityService.execStatus(activityId, beforeStatus, afterStatus);return new Result("0000", "变更状态成功");} else {return new Result("0001", "变更状态拒绝");}}// 2. 审核通过 -> 拒绝、关闭、活动中if (Status.Pass.equals(beforeStatus)) {if (Status.Refuse.equals(afterStatus) || Status.Doing.equals(afterStatus) || Status.Close.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.Close.equals(afterStatus)) {ActivityService.execStatus(activityId, beforeStatus, afterStatus);return new Result("0000", "变更状态成功");} else {return new Result("0001", "变更状态拒绝");}}// 4. 活动中 -> 关闭if (Status.Doing.equals(beforeStatus)) {if (Status.Close.equals(afterStatus)) {ActivityService.execStatus(activityId, beforeStatus, afterStatus);return new Result("0000", "变更状态成功");} else {return new Result("0001", "变更状态拒绝");}}// 5. 活动关闭 -> 开启if (Status.Close.equals(beforeStatus)) {if (Status.Open.equals(afterStatus)) {ActivityService.execStatus(activityId, beforeStatus, afterStatus);return new Result("0000", "变更状态成功");} else {return new Result("0001", "变更状态拒绝");}}// 6. 活动开启 -> 关闭if (Status.Open.equals(beforeStatus)) {if (Status.Close.equals(afterStatus)) {ActivityService.execStatus(activityId, beforeStatus, afterStatus);return new Result("0000", "变更状态成功");} else {return new Result("0001", "变更状态拒绝");}}return new Result("0001", "非可处理的活动状态变更");}
}
- 从这个代码实现的结构看,从上到下是一整篇的
if-else
。 - 这样的面向过程式开发方式,对于不需要改动代码,也不需要二次迭代的,还是可以使用的(
但基本不可能不迭代
)。 - 而且随着状态和需求变化,会越来越难为维护,后面的人也不好看懂并且很容易填充其他的流程进去。
3.3 单元测试
ApiTest.java
package com.lino.design.test;import com.alibaba.fastjson.JSON;
import com.lino.design.ActivityExecStatusController;
import com.lino.design.ActivityService;
import com.lino.design.Result;
import com.lino.design.Status;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @description: 单元测试*/
public class ApiTest {private Logger logger = LoggerFactory.getLogger(ApiTest.class);@Testpublic void test() {// 初始化数据String activityId = "100001";ActivityService.init(activityId, Status.Editing);ActivityExecStatusController activityExecStatusController = new ActivityExecStatusController();Result resultRefuse = activityExecStatusController.execStatus(activityId, Status.Editing, Status.Refuse);logger.info("测试结果(编辑中To审核拒绝):{}", JSON.toJSONString(resultRefuse));Result resultCheck = activityExecStatusController.execStatus(activityId, Status.Editing, Status.Check);logger.info("测试结果(编辑中To提交审核):{}", JSON.toJSONString(resultCheck));}
}
- 这个测试主要包括两个功能的验证,一个是从
编辑中
到审核拒绝
,另外一个是从编辑中
到提交审核
。 - 因为从我们的场景流程中可以看到,
编辑中
的活动是不能直接到审核拒绝
,这中间还需要提审
。
测试结果
17:03:34.528 [main] INFO com.lino.design.test.ApiTest - 测试结果(编辑中To审核拒绝):{"code":"0001","info":"变更状态拒绝"}
17:03:34.534 [main] INFO com.lino.design.test.ApiTest - 测试结果(编辑中To提交审核):{"code":"0000","info":"变更状态成功"}
四、改善代码:状态模式
💡 重构的重点往往是处理掉
if-else
,而想处理掉if-else
基本离不开接口与抽象类,另外还需要重新改造代码结构。
4.0 引入依赖
<dependencies><dependency><groupId>com.lino</groupId><artifactId>design-20.0-0</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>
4.1 工程结构
design-19.0-2
|——src|——main|--java|--com.lino.design|--impl| |--CheckState.java| |--CloseState.java| |--DoingState.java| |--EditingState.java| |--OpenState.java| |--PassState.java| |--RefuseState.java|-State.java|-StateHandler.java|--test|--com.lino.design.test|-ApiTest.java
4.2 状态模式结构图
- 以上是状态模式的整个工程结构模型,
State
是一个抽象类,定义了各种操作接口(提审、审核、拒审等
)。 - 右侧的不同颜色状态与我们场景模拟中的颜色保持一致,是各种状态流程流转的实现操作。
- 这里的实现有一个关键点就是每一种状态到下一个状态,都分配到各个实现方法中控制,也就不需要
if
语言进行判断了。
- 这里的实现有一个关键点就是每一种状态到下一个状态,都分配到各个实现方法中控制,也就不需要
- 最后是
StateHandler
对状态流程的统一处理,里面提供Map
结构的各项服务接口调用,也就避免了使用if
判断各项状态转变的流程。
4.3 活动状态变更流程
4.3.1 定义状态抽象类
State.java
package com.lino.design;/*** @description: 活动状态抽象类*/
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);
}
- 整个接口中提供了各项状态流转服务的接口,例如:
活动提审
、审核通过
、审核拒绝
、撤审撤销
、活动关闭
、活动开启
、**活动执行
。 - 在这些方法中所有的入参都是一样的,
activityId
(活动ID)、currentStatus
(当前状态),只有他们的具体实现是不同的。
4.3.2 待审核状态流转实现
CheckState.java
package com.lino.design.impl;import com.lino.design.ActivityService;
import com.lino.design.Result;
import com.lino.design.State;
import com.lino.design.Status;/*** @description: 待审核*/
public class CheckState extends State {@Overridepublic Result arraignment(String activityId, Enum<Status> currentStatus) {return new Result("0001", "待审核状态不可重复提审");}@Overridepublic Result checkPass(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Pass);return new Result("0000", "活动审核通过完成");}@Overridepublic Result checkRefuse(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Refuse);return new Result("0000", "活动审核拒绝完成");}@Overridepublic Result checkRevoke(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Editing);return new Result("0000", "活动审核撤销回到编辑中");}@Overridepublic Result close(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Close);return new Result("0000", "活动审核关闭完成");}@Overridepublic Result open(String activityId, Enum<Status> currentStatus) {return new Result("0001", "非关闭活动不可开启");}@Overridepublic Result doing(String activityId, Enum<Status> currentStatus) {return new Result("0001", "待审核活动不可执行活动中变更");}
}
4.3.3 活动关闭状态流转实现
CloseState.java
package com.lino.design.impl;import com.lino.design.ActivityService;
import com.lino.design.Result;
import com.lino.design.State;
import com.lino.design.Status;/*** @description: 活动关闭*/
public class CloseState extends State {@Overridepublic Result arraignment(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动关闭不可提审");}@Overridepublic Result checkPass(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动关闭不可审核通过");}@Overridepublic Result checkRefuse(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动关闭不可审核拒绝");}@Overridepublic Result checkRevoke(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动关闭不可撤销审核");}@Overridepublic Result close(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动关闭不可重复关闭");}@Overridepublic Result open(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Open);return new Result("0000", "活动开启完成");}@Overridepublic Result doing(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动关闭不可变更活动中");}
}
4.3.4 活动中状态流转实现
DoingState.java
package com.lino.design.impl;import com.lino.design.ActivityService;
import com.lino.design.Result;
import com.lino.design.State;
import com.lino.design.Status;/*** @description: 活动中*/
public class DoingState extends State {@Overridepublic Result arraignment(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动中不可提审");}@Overridepublic Result checkPass(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动中不可审核通过");}@Overridepublic Result checkRefuse(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动中不可审核拒绝");}@Overridepublic Result checkRevoke(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动中不可撤销审核");}@Overridepublic Result close(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Close);return new Result("0000", "活动关闭完成");}@Overridepublic Result open(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动中不可开启");}@Overridepublic Result doing(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动中不可重复执行");}
}
4.3.5 编辑中状态流转实现
EditingState.java
package com.lino.design.impl;import com.lino.design.ActivityService;
import com.lino.design.Result;
import com.lino.design.State;
import com.lino.design.Status;/*** @description: 编辑中*/
public class EditingState extends State {@Overridepublic Result arraignment(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Check);return new Result("0000", "活动提审成功");}@Overridepublic Result checkPass(String activityId, Enum<Status> currentStatus) {return new Result("0001", "编辑中不可审核通过");}@Overridepublic Result checkRefuse(String activityId, Enum<Status> currentStatus) {return new Result("0001", "编辑中不可审核拒绝");}@Overridepublic Result checkRevoke(String activityId, Enum<Status> currentStatus) {return new Result("0001", "编辑中不可撤销审核");}@Overridepublic Result close(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Close);return new Result("0000", "活动关闭完成");}@Overridepublic Result open(String activityId, Enum<Status> currentStatus) {return new Result("0001", "非关闭活动不可开启");}@Overridepublic Result doing(String activityId, Enum<Status> currentStatus) {return new Result("0001", "编辑活动中不可执行活动中变更");}
}
4.3.6 活动开启状态流转实现
OpenState.java
package com.lino.design.impl;import com.lino.design.ActivityService;
import com.lino.design.Result;
import com.lino.design.State;
import com.lino.design.Status;/*** @description: 活动开启*/
public class OpenState extends State {@Overridepublic Result arraignment(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动开启不可提审");}@Overridepublic Result checkPass(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动开启不可审核通过");}@Overridepublic Result checkRefuse(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动开启不可审核拒绝");}@Overridepublic Result checkRevoke(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动开启不可撤销审核");}@Overridepublic Result close(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Close);return new Result("0000", "活动关闭完成");}@Overridepublic Result open(String activityId, Enum<Status> currentStatus) {return new Result("0001", "活动不可重复开启");}@Overridepublic Result doing(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Doing);return new Result("0000", "活动变更活动中完成");}
}
4.3.7 审核通过状态流转实现
PassState.java
package com.lino.design.impl;import com.lino.design.ActivityService;
import com.lino.design.Result;
import com.lino.design.State;
import com.lino.design.Status;/*** @description: 审核通过*/
public class PassState extends State {@Overridepublic Result arraignment(String activityId, Enum<Status> currentStatus) {return new Result("0001", "已审核状态不可重复提审");}@Overridepublic Result checkPass(String activityId, Enum<Status> currentStatus) {return new Result("0001", "已审核状态不可重复审核");}@Overridepublic Result checkRefuse(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Refuse);return new Result("0000", "活动审核拒绝完成");}@Overridepublic Result checkRevoke(String activityId, Enum<Status> currentStatus) {return new Result("0001", "审核通过不可撤销(可先拒绝审核)");}@Overridepublic Result close(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Close);return new Result("0000", "活动关闭完成");}@Overridepublic Result open(String activityId, Enum<Status> currentStatus) {return new Result("0001", "非关闭活动不可开启");}@Overridepublic Result doing(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Doing);return new Result("0000", "活动变更活动中完成");}
}
4.3.8 审核拒绝状态流转实现
RefuseState.java
package com.lino.design.impl;import com.lino.design.ActivityService;
import com.lino.design.Result;
import com.lino.design.State;
import com.lino.design.Status;/*** @description: 审核拒绝*/
public class RefuseState extends State {@Overridepublic Result arraignment(String activityId, Enum<Status> currentStatus) {return new Result("0001", "已审核状态不可重复提审");}@Overridepublic Result checkPass(String activityId, Enum<Status> currentStatus) {return new Result("0001", "已审核状态不可重复审核");}@Overridepublic Result checkRefuse(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Refuse);return new Result("0000", "活动审核拒绝不可重复审核");}@Overridepublic Result checkRevoke(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Editing);return new Result("0000", "撤销审核完成");}@Overridepublic Result close(String activityId, Enum<Status> currentStatus) {ActivityService.execStatus(activityId, currentStatus, Status.Close);return new Result("0000", "活动审核关闭完成");}@Overridepublic Result open(String activityId, Enum<Status> currentStatus) {return new Result("0001", "非关闭活动不可开启");}@Overridepublic Result doing(String activityId, Enum<Status> currentStatus) {return new Result("0001", "审核拒绝不可执行活动为进行中");}
}
- 这里提供了七个具体实现类。
待审核
、活动关闭
、活动中
、编辑中
、活动开启
、审核通过
、审核拒绝
。 - 在每个实现类中,每个方法对于不同的类中有不同的实现,也就是不同状态下一步流转操作已经可以在每一个方法中具体控制了。
4.3.9 活动执行者
StateHandler.java
package com.lino.design;import com.lino.design.impl.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @description: 活动执行者*/
public class StateHandler {private Map<Enum<Status>, State> stateMap = new ConcurrentHashMap<>();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());}/*** 活动提审** @param activityId 活动ID* @param currentStatus 当前状态* @return 执行结果*/public Result arraignment(String activityId, Enum<Status> currentStatus) {return stateMap.get(currentStatus).arraignment(activityId, currentStatus);}/*** 审核通过** @param activityId 活动ID* @param currentStatus 当前状态* @return 执行结果*/public Result checkPass(String activityId, Enum<Status> currentStatus) {return stateMap.get(currentStatus).checkPass(activityId, currentStatus);}/*** 审核拒绝** @param activityId 活动ID* @param currentStatus 当前状态* @return 执行结果*/public Result checkRefuse(String activityId, Enum<Status> currentStatus) {return stateMap.get(currentStatus).checkRefuse(activityId, currentStatus);}/*** 撤审撤销** @param activityId 活动ID* @param currentStatus 当前状态* @return 执行结果*/public Result checkRevoke(String activityId, Enum<Status> currentStatus) {return stateMap.get(currentStatus).checkRevoke(activityId, currentStatus);}/*** 活动关闭** @param activityId 活动ID* @param currentStatus 当前状态* @return 执行结果*/public Result close(String activityId, Enum<Status> currentStatus) {return stateMap.get(currentStatus).close(activityId, currentStatus);}/*** 活动开启** @param activityId 活动ID* @param currentStatus 当前状态* @return 执行结果*/public Result open(String activityId, Enum<Status> currentStatus) {return stateMap.get(currentStatus).open(activityId, currentStatus);}/*** 活动执行** @param activityId 活动ID* @param currentStatus 当前状态* @return 执行结果*/public Result doing(String activityId, Enum<Status> currentStatus) {return stateMap.get(currentStatus).doing(activityId, currentStatus);}
}
- 这是对状态服务的统一控制中心,可以看到子啊构造函数中提供了所有状态和实现的具体关联,放到
Map
数据结构中。 - 同时提供了不同名称的接口操作类,让外部调用方可以更加容易的使用此项功能接口,而不需要像在之前还得传两个状态来判断。
4.4 单元测试
4.4.1 编辑中到提审活动测试验证
ApiTest.java
@Test
public void test_Editing2Arraignment() {String activityId = "100001";ActivityService.init(activityId, Status.Editing);StateHandler stateHandler = new StateHandler();Result result = stateHandler.arraignment(activityId, Status.Editing);logger.info("测试结果(编辑中To提审活动):{}", JSON.toJSONString(result));logger.info("活动信息:{} 状态:{}", JSON.toJSONString(ActivityService.queryActivityInfo(activityId)), JSON.toJSONString(ActivityService.queryActivityStatus(activityId)));
}
测试结果
20:48:10.752 [main] INFO com.lino.design.test.ApiTest - 测试结果(编辑中To提审活动):{"code":"0000","info":"活动提审成功"}
20:48:10.788 [main] INFO com.lino.design.test.ApiTest - 活动信息:{"activityId":"100001","activityName":"早起学习打卡领奖活动","beginTime":1675860490764,"endTime":1675860490764,"status":"Check"} 状态:"Check"
- 从编辑中到提审活动到状态流转,测试验证结果显示正常。
4.4.2 编辑中到开启活动测试验证
ApiTest.java
@Test
public void test_Editing2Open() {String activityId = "100001";ActivityService.init(activityId, Status.Editing);StateHandler stateHandler = new StateHandler();Result result = stateHandler.open(activityId, Status.Editing);logger.info("测试结果(编辑中To开启活动):{}", JSON.toJSONString(result));logger.info("活动信息:{} 状态:{}", JSON.toJSONString(ActivityService.queryActivityInfo(activityId)), JSON.toJSONString(ActivityService.queryActivityStatus(activityId)));
}
测试结果
20:48:57.997 [main] INFO com.lino.design.test.ApiTest - 测试结果(编辑中To开启活动):{"code":"0001","info":"非关闭活动不可开启"}
20:48:58.041 [main] INFO com.lino.design.test.ApiTest - 活动信息:{"activityId":"100001","activityName":"早起学习打卡领奖活动","beginTime":1675860538008,"endTime":1675860538008,"status":"Editing"} 状态:"Editing"
- 从编辑中到开启活动到状态流转可以看到,测试结果是拒绝-
非关闭活动不可开启
。 - 按照流程流转结果,预期结果正常,不能从编辑中直接到活动开启,需要经过审批阶段。
4.4.3 审批拒绝到活动中测试验证
ApiTest.java
@Test
public void test_Editing2Doing() {String activityId = "100001";ActivityService.init(activityId, Status.Refuse);StateHandler stateHandler = new StateHandler();Result result = stateHandler.doing(activityId, Status.Refuse);logger.info("测试结果(拒绝To活动中):{}", JSON.toJSONString(result));logger.info("活动信息:{} 状态:{}", JSON.toJSONString(ActivityService.queryActivityInfo(activityId)), JSON.toJSONString(ActivityService.queryActivityStatus(activityId)));
}
测试结果
20:49:51.974 [main] INFO com.lino.design.test.ApiTest - 测试结果(拒绝To活动中):{"code":"0001","info":"审核拒绝不可执行活动为进行中"}
20:49:52.015 [main] INFO com.lino.design.test.ApiTest - 活动信息:{"activityId":"100001","activityName":"早起学习打卡领奖活动","beginTime":1675860591979,"endTime":1675860591979,"status":"Refuse"} 状态:"Refuse"
- 从审批拒绝到活动中到状态流转可以看到,同样是拒绝-
审批拒绝不可执行活动中
为进行中,满足预期。
4.4.4 审批拒绝到撤审测试验证
ApiTest.java
@Test
public void test_Editing2Revoke() {String activityId = "100001";ActivityService.init(activityId, Status.Refuse);StateHandler stateHandler = new StateHandler();Result result = stateHandler.checkRevoke(activityId, Status.Refuse);logger.info("测试结果(拒绝To撤审):{}", JSON.toJSONString(result));logger.info("活动信息:{} 状态:{}", JSON.toJSONString(ActivityService.queryActivityInfo(activityId)), JSON.toJSONString(ActivityService.queryActivityStatus(activityId)));
}
测试结果
20:50:39.921 [main] INFO com.lino.design.test.ApiTest - 测试结果(拒绝To撤审):{"code":"0000","info":"撤销审核完成"}
20:50:39.960 [main] INFO com.lino.design.test.ApiTest - 活动信息:{"activityId":"100001","activityName":"早起学习打卡领奖活动","beginTime":1675860639937,"endTime":1675860639937,"status":"Editing"} 状态:"Editing"
- 从审批拒绝到撤审到状态流转可以看到,按照状态到流转约定,审批拒绝后可以撤审,测试结果满足预期。
💡 综上,以上四个测试类分别模拟了不同状态之间到有效流转和拒绝流转,不同的状态服务处理不同的服务内容。
五、总结:状态模式
- 从以上两种方式对一个需求的实现对比可以看到,在使用设计模式处理后,已经没有了
if-else
,代码的结构也更加清晰,易于扩展。 - 在实现结构的编码方式上,可以看到不再是面向过程的编程,而是面向对象的编程。
- 状态模式的优点:
- 状态模式满足了单一职责和开闭原则。
- 当只有满足这种结构时,才会发现代码的扩展是容易的,也就是增加或修改功能不会影响整体。
- 状态模式的缺点:
- 如果状态和各项流转较多,就会产生较多的实现类。因此,可能会给代码的实现增加时间成本。
- 因为如果遇到这种场景可以按需评估投入回报率,主要在于是否会经常修改,是否可以做成组件化,抽离业务功能与非业务功能。