[本文原文链接地址:http://nullpointer.pwhttps://img.qb5200.com/download-x/design-patterns-state.html](http://nullpointer.pwhttps://img.qb5200.com/download-x/design-patterns-state.html)
本文以运营活动状态转换为例,结合 Spring 演示状态模式的实践应用。
类型:行为型模式
意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
主要解决:一个对象存在多个状态,每个状态行为不同,状态可以相互转换。
使用场景:1、行为随状态改变而改变的场景。 2、减少 switch..case 以及 if...else
[设计模式系列文章目录 ](http://nullpointer.pwhttps://img.qb5200.com/download-x/design-patterns.html)
## 角色
- State:抽象状态角色,负责对象状态定义,并且封装环境角色以实现状态切换。
- Context:环境角色,定义客户端需要的接口,并且负责具体状态的切换。
- ConcreteState:具体状态角色,当前状态要做的事情,以及当前状态如何转换其他状态。
## UML
![](http://www.plantuml.com/plantuml/svg/LSx12O0m40J0_rLnJqNIWOXW2LPmnWCVCM9k1K7iNIKe-DxPPLk396np81_4ZBibGdVmGSHSST9rKqDHqaaaWo691sVQGw0tVDmaSGoQsJVaaq9VzIJl-EAQtAQSrixzFRKQn_lK1G00)
## 实战
本文以运营活动状态为例,结合 Spring 演示状态模式的实践应用。
运营活动创建初始状态为草稿状态,编辑好活动之后,运营会后台启用活动,此时活动状态为已启用;
当达到活动开始时间时,定时任务会将活动状态置为进行中;
当达到活动结束时间时,定时任务会将活动状态置为已结束。
进行中的活动也可能会因为某些原因需要手动停用,此时活动状态置为已停用。
状态之间有着严格的前置校验,比如草稿状态可以继续保存为草稿,也可以进行启动,但不能直接切换为进行中,可以直接编辑切换回草稿箱状态;比如已停用的状态只有在启用之后才能被置为进行中。
活动状态的切换约束如下图:
| 新状态→
当前状态↓ | 草稿箱 | 已启用 | 进行中 | 已停用 | 已结束 |
| :---------------------: | :----: | :----: | :----: | :----: | :----: |
| 草稿箱 | ✅ | ✅ | ❌ | ❌ | ❌ |
| 已启用 | ✅ | ❌ | ✅ | ✅ | ❌ |
| 进行中 | ❌ | ❌ | ❌ | ✅ | ✅ |
| 已停用 | ❌ | ✅ | ❌ | ❌ | ❌ |
| 已结束 | ❌ | ❌ | ❌ | ❌ | ❌ |
如果不采取状态模式,可能写出的代码就是不断使用 if 判断前置状态是否符合规则,当增加了新的状态,需要改动判断的地方,从而可能引入了 Bug。
### 本文示例 UML 图
![](http://www.plantuml.com/plantuml/svg/vLR1Ri8m3BtxAonnwJJr2q0Jqs3Ipdo1svejKabGOofGuTyfGOMswhAC8pqcUNxnUtQipArG8RjD3fHOAIWLJ7Eo5jzJKQMImqf862k0oMthmsZXlI1rrm28hrWQbQ5bwO6ZFu9VN73L977wgdU_kK0vR3dg7oR6v4mQBPMyA6XzPx_H_XR2cfASm_7Edd1ufkp_-DTAA_ipX3y1T2lHE5VLL76lFjHUJGSBuOuYJzWrnM1lmnmDyZ7GlGSySmgQ5hvm3FJLSobUkkH69NbrkNPKYXTNnb5f_dJG9vSC_M5luhVkg8Vk19yTutXWslbGhGdXWzrVbVVALYVwSAtgmPnrLlyupW00)
## 示例代码
### 定义抽象状态角色
```java
public abstract class ActivityState {
// 抽象状态角色需要持有环境上下文对象
protected ActivityContext activityContext;
public void setActivityContext(ActivityContext activityContext) {
this.activityContext = activityContext;
}
public abstract Integer type();
/**
* 判断是否是当前状态
*/
protected boolean isSameStatus(Activity activity) {
return type().equals(activity.getStatus());
}
/**
* 保存草稿
*/
public abstract boolean saveDraft(Activity activity);
/**
* 启用
*/
public abstract boolean enable(Activity activity);
/**
* 开始
*/
public abstract boolean start(Activity activity);
/**
* 停用
*/
public abstract boolean disable(Activity activity);
/**
* 停止
*/
public abstract boolean finish(Activity activity);
}
```
### 定义环境角色
```java
public class ActivityContext {
// 持有抽象状态角色引用
private ActivityState activityState;
public void setActivityState(ActivityState activityState) {
this.activityState = activityState;
this.activityState.setActivityContext(this);
}
public boolean saveDraft(Activity activity) {
// 委托具体的状态角色
return this.activityState.saveDraft(activity);
}
public boolean enable(Activity activity) {
return this.activityState.enable(activity);
}
public boolean start(Activity activity) {
return this.activityState.start(activity);
}
public boolean disable(Activity activity) {
return this.activityState.disable(activity);
}
public boolean finish(Activity activity) {
return this.activityState.finish(activity);
}
}
```
### 定义具体状态角色
因为本文示例具体状态角色有很多,因此只列举一个开启状态角色举例参考,更多代码可以参考本文对应的 GitHub 示例代码
```java
@Component
public class ActivityEnableState extends ActivityState {
@Resource
private ActivityDraftState activityDraftState;
@Resource
private ActivityStartState activityStartState;
@Resource
private ActivityDisableState activityDisableState;
@Override
public Integer type() {
return ActivityStateEnum.ENABLE.getCode();
}
@Override
public boolean saveDraft(Activity activity) {
super.activityContext.setActivityState(activityDraftState);
return activityContext.saveDraft(activity);
}
@Override
public boolean enable(Activity activity) {
// 如果当前状态已经是 enable 了,则无法再次 enable
if (isSameStatus(activity)) {
return false;
}
activity.setStatus(type());
//TODO 更新数据库
return true;
}
@Override
public boolean start(Activity activity) {
super.activityContext.setActivityState(activityStartState);
return activityContext.start(activity);
}
@Override
public boolean disable(Activity activity) {
super.activityContext.setActivityState(activityDisableState);
return activityContext.disable(activity);
}
@Override
public boolean finish(Activity activity) {
// 非进行中的活动状态,不允许直接进行 finish
return false;
}
}
```
### 封装具体状态实例工厂
状态角色应该是单例的,结合 Spring 与工厂模式对实例进行封装,方便根据数据库的 status 值获取对应的状态角色实例。
```java
@Component
public class ActivityStateFactory implements ApplicationContextAware {
public static final Map