# 设计模式之状态模式
> 来源自《重学Java设计模式》[链接](https://pan.baidu.com/s/1dUMCff7S5Qb-vsJFw28AZg
)提取码:ytc3
## 状态模式

状态模式描述的是一个行为下的多种状态变更,比如我们最常见的一个网站的页面,在你登录与不登录下展示的内容是略有差异的,而这种`登录`与`不登录`就是我们通过改变状态,而让整个行为发生了变化。
## 案例场景模拟

在本案例中我们模拟营销活动审核状态流转场景(一个活动的上线时多层级审核上线的)
在上图中也可以看到我们的流程节点中包括了各个状态到下一个状态流转的关联条件,比如:审核通过才能到活动中,而不能从编辑中直接到活动中,而这些状态的转变即使我们要完成的场景处理。
### 场景模拟工程
- 在这个模拟工程里我们提供了三个类,包括:状态枚举(`Status`)、活动对象(`ActivityInfo`)、活动服务(`ActivityService`)
### 代码实现
#### 基本活动信息
```java
public class ActivityInfo {
private String activityId; // 活动ID
private String activityName; // 活动名称
private Enum<Status> status; // 活动状态
private Date beginTime; // 开始时间
private Date endTime; // 结束时间
public String getActivityId() {
return activityId;
}
public void setActivityId(String activityId) {
this.activityId = activityId;
}
public String getActivityName() {
return activityName;
}
public void setActivityName(String activityName) {
this.activityName = activityName;
}
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;
}
}
```
#### 活动枚举状态
```java
public enum Status {
// 1创建编辑、2待审核、3审核通过(任务扫描成活动中)、4审核拒绝(可以撤审到编辑状态)、5活动中、6活动关闭、7活动开启(任务扫描成活动中)
Editing, Check, Pass, Refuse, Doing, Close, Open
}
```
#### 活动服务接口
```java
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);
activityInfo.setBeginTime(new Date());
activityInfo.setEndTime(new Date());
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));
activityInfo.setBeginTime(new Date());
activityInfo.setEndTime(new Date());
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);
}
}
```
- 在这个类中提供了活动的查询和状态变更接口:`queryActivityInfo`、`queryActivityStatus`、`execStatus`。
- 同时使用Map的结构来记录活动ID和状态变化信息,另外还有init方法来初始化活动数据。实际的开发中这类信息基本都是从`数据库`或者`Redis`中获取。
## 常规实现
```java
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", "非可处理的活动状态变更");
}
}
```
```java
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;
}
}
```
- 这样的面向过程式开发方式,对于不需要改动代码,也不需要二次迭代的,还是可以使用的(`但是基本不可能不迭代`)。而且随着状态和需求的变化,会越来越难以维护,后面的人也不好看懂并且很容易填充其他流程进去。
### 测试验证
#### 测试类
```java
public class ApiTest {
private Logger logger = LoggerFactory.getLogger(ApiTest.class);
@Test
public 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));
}
}
```
result:
```java
13:10:34.078 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果(编辑中To审核拒绝):{"code":"0001","info":"变更状态拒绝"}
13:10:34.109 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果(编辑中To提交审核):{"code":"0000","info":"变更状态成功"}
```
## 使用状态模式
### 状态模式结构模型

- 以上是状态模式的整个工程结构模型,State是一个抽象类,定义了各种操作接口。
- 右侧不同颜色状态与我们场景模拟中的颜色保持一致,是各种状态流程流转的实现操作。这里的实现有一个关键点就是每一种状态到下一个状态,都分配到各个实现方法种控制,也就不需要`ifelse`进行判断了
- 最后是`StateHandler`对状态流程的统一处理,里面提供`Map`结构的各种服务接口调用,也就避免了使用`if`判断各项状态转变的流程。
### 代码实现
#### 定义状态抽象类
```java
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(`当前状态`),只有他们的具体实现不同的。
#### 状态流转实现
编辑
```java
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", "编辑中活动不可执行活动中变更");
}
}
```
提审
```java
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", "待审核活动不可执行活动中变更");
}
}
```
#### 状态处理服务
```java
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数据结构中。
- 同时提供了不同名称的接口操作类,让外部调用方可以更加容易的使用此项功能接口,而不需要像在例子中还得传两个状态判断。
#### 测试验证
**编辑测试类1**
```java
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.queryActivityInfo(activityId).getStatus()));
}
```
**结果**
```java
14:37:40.334 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果(编辑中To提审活动):{"code":"0000","info":"活动提审成功"}
14:37:40.347 [main] INFO org.itstack.demo.design.test.ApiTest - 活动信息:{"activityId":"100001","activityName":"早起学习打卡领奖活动","beginTime":1609828660338,"endTime":1609828660338,"status":"Check"} 状态:"Check"
Process finished with exit code 0
```
**编辑测试类2**
```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.queryActivityInfo(activityId).getStatus()));
}
```
**结果**
```java
14:39:34.122 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果(编辑中To开启活动):{"code":"0001","info":"非关闭活动不可开启"}
14:39:34.137 [main] INFO org.itstack.demo.design.test.ApiTest - 活动信息:{"activityId":"100001","activityName":"早起学习打卡领奖活动","beginTime":1609828774127,"endTime":1609828774127,"status":"Editing"} 状态:"Editing"
Process finished with exit code 0
```
**审核拒绝测试类**
```java
@Test
public void test_Refuse2Doing() {
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.queryActivityInfo(activityId).getStatus()));
}
```
**结果**
```java
14:41:21.034 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果(拒绝To活动中):{"code":"0001","info":"审核拒绝不可执行活动为进行中"}
14:41:21.054 [main] INFO org.itstack.demo.design.test.ApiTest - 活动信息:{"activityId":"100001","activityName":"早起学习打卡领奖活动","beginTime":1609828881042,"endTime":1609828881042,"status":"Refuse"} 状态:"Refuse"
Process finished with exit code 0
```
**拒绝撤销测试类**
```java
@Test
public void test_Refuse2Revoke() {
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.queryActivityInfo(activityId).getStatus()));
}
```
**结果**
```java
14:45:06.332 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果(拒绝To撤审):{"code":"0000","info":"撤销审核完成"}
14:45:06.350 [main] INFO org.itstack.demo.design.test.ApiTest - 活动信息:{"activityId":"100001","activityName":"早起学习打卡领奖活动","beginTime":1609829106342,"endTime":1609829106342,"status":"Editing"} 状态:"Editing"
Process finished with exit code 0
```
## 总结
- 从以上的两种方式对一个需求的实现中可以看到,在第二种使用设计模式处理后已经没有了`ifelse`,代码的结构也更加清晰易于扩展。这就是设计模式的好处,可以非常强大的改变原有代码的结构,让以后的扩展和维护都变得容易。
- 在实现结构的编码方式上可以看到这不再是面向过程的编程,而是面向对象的结构,并且这样的设计模式满足了`单一职责`和`开闭原则`,当你只有满足这样的结构下才会发现代码的扩展是容易的,也就是增加和修改功能不会影响到整体的变化。

设计模式之状态模式