状态模式介绍

状态模式描述的是一个行为下的多种状态变更。例如网站的页面,在登录与没有登录的状态下我们看见的内容有时候是不一样的,如果不登录就不能展示某些页面。

活动审批状态转换场景

比如一个流程是由多个层级审批上线的

image-20211216222325467

可以看到流程节点包括了各个状态到下一个状态的关联条件,比如审批通过才能到活动中,而不能从编辑中直接到活动中,而这些状态的转变就是要完成的场景。

很多程序员都开发过类似的业务,需要对活动或者一些配置进行审批才能对外发布,而审批的过程往往会随着系统的额重要程度的提高而设立多级控制,以保证一个活动可以安全上线,避免造成损失。当然,有时会用一些审批流的过程配置,也非常便于开发类似的流程,可以在配置中设定某个节点的审批人员。

场景模拟工程

基本活动信息

1
2
3
4
5
6
7
8
9
10
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ActivityInfo {
private String activityId;
private String activityName;
private Date beginTime;
private Date endTime;
private Enum<Status> status;
}

活动枚举状态

1
2
3
4
5
6
7
8
9
10
11
package com.bestrookie;
public enum Status {
//1 创建编辑,2、daishenpi 3、审批通过(任务扫描成活动中),4、审批拒绝(可以撤审到编辑状态),5、活动中 6、活动关闭 7、活动开启(任务扫描成活动中)
Editing,
Check,
Pass,
Refuse,
Doing,
Close,
Open
}

这里是活动的枚举:1、创建编辑 2、待审批 3、审批通过 4、审批拒绝 5、活动中 6、活动关闭 7、活动开启

活动服务接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.bestrookie;
import com.bestrookie.pojo.ActivityInfo;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author bestrookie
* @version 1.0
* @date 2021/12/14 22:38
*/
public class ActivityService {
private static Map<String, Enum<Status>> statusMap = new ConcurrentHashMap<>();
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.setEndTime(new Date());
activityInfo.setBeginTime(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 变更后状态
*/
public static synchronized void execStatus(String activityId,Enum<Status> beforeStatus,Enum<Status> afterStatus){
if (!beforeStatus.equals(statusMap.get(activityId))){
return;
}
statusMap.put(activityId,afterStatus);
}
}

这个静态类提供了活动的查询和状态变更接口 queryActivtiyInfo、queryActivtyStatus和execStatus。通过使用Map结构记录活动ID和状态变化信息,另外init方法初始活动数据。在实际的开发中,这类信息基本都是从数据库或Redis中获取。

违背设计模式实现

对于各种状态变更的情况,最直接的方式是使用if和else判断。每一个状态可以流转到下一个状态,使用嵌套的if语句实现。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.bestrookie;
import com.bestrookie.pojo.ActivityInfo;
import com.bestrookie.util.Result;
/**
* @author bestrookie
* @date 2021/12/15 8:21 下午
*/
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){
// 编辑中 -> 提审、关闭
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.Close.equals(afterStatus) || 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.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("0002","非可处理的活动状态变更");
}
}

从代码实现的结构即可,从上到下用了很多的if…else。对于不需要改动也不需要二次迭代的代码,这种面向过程式的开发方式还是可以使用的。但在真实场景中,基本不可能不迭代,随着状态和需求的变化,会越来越难以维护,后来的同时也不容易看懂,很容易将其他的流程填充进去。

状态模式重构代码

重构的重点往往是处理if…else,这离不开接口与抽象类,另外还需要重新改造代码结构。

image-20211216231216235

其中,State是一个抽象类,定义了各种操作接口,包括提审、审批和拒审等。右侧的不同状态与前面的各种状态保持一致,是各种状态流程保持一致,是各种状态流程流转的实现操作。这里有一个关键点,对于每一种状态到下一个状态,都设置为在各个实现方法中控制,不需要if语句判断。最后,StateHandler对状态流程统一处理,里面提供Map结构的各项接口调用,避免使用if语句判断状态转变的流程。

定义抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.bestrookie.design;
import com.bestrookie.Status;
import com.bestrookie.util.Result;
public abstract class State {
/**
* 活动提审
* @param activityId 活动ID
* @param currentStatus 当前状态
* @return 执行结果
*/
public abstract Result arraignment(String activityId, Enum<Status> currentStatus);

/**
* 审批通过
* @param activityId 活动ID
* @param currenStatus 当前状态
* @return 执行结果
*/
public abstract Result checkPass(String activityId,Enum<Status> currenStatus);

/**
* 审批拒绝
* @param activityId 活动ID
* @param currenStatus 当前状态
* @return 执行结果
*/
public abstract Result checkRefuse(String activityId,Enum<Status> currenStatus);

/**
* 审批赊销
* @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、currentStatus,只有各自的具体实现方式不同的。

部分状态流转实现

编辑状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.bestrookie.design.event;

import com.bestrookie.ActivityService;
import com.bestrookie.Status;
import com.bestrookie.design.State;
import com.bestrookie.util.Result;


/**
* @author bestrookie
* @version 1.0
* @date 2021/12/15 22:55
*/
public class EditingState extends State {
@Override
public Result arraignment(String activityId, Enum<Status> currentStatus) {
ActivityService.execStatus(activityId,currentStatus,Status.Check);
return new Result("0000","活动提审成功");
}

@Override
public Result checkPass(String activityId, Enum<Status> currenStatus) {
return new Result("0001","编辑中不可审批通过");
}

@Override
public Result checkRefuse(String activityId, Enum<Status> currenStatus) {
return new Result("0001","编辑中不可审批拒绝");
}

@Override
public Result checkRevoke(String activityId, Enum<Status> currentStatus) {
return new Result("0001","编辑中不可撤销审批");
}

@Override
public Result close(String activityId, Enum<Status> currentStatus) {
ActivityService.execStatus(activityId,currentStatus,Status.Close);
return new Result("0000","活动关闭成功");
}

@Override
public Result open(String activityId, Enum<Status> currentStatus) {
return new Result("0001","非关闭活动不可开启");
}

@Override
public Result doing(String activityId, Enum<Status> currentStatus) {
return new Result("0001","编辑中活动不渴执行活动中变更");
}
}
提审状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.bestrookie.design.event;

import com.bestrookie.ActivityService;
import com.bestrookie.Status;
import com.bestrookie.design.State;
import com.bestrookie.util.Result;

/**
* @author bestrookie
* @version 1.0
* @date 2021/12/15 23:30
*/
public class CheckState extends State {
@Override
public Result arraignment(String activityId, Enum<Status> currentStatus) {
return new Result("0001","待审批状态不可重复提审");
}

@Override
public Result checkPass(String activityId, Enum<Status> currenStatus) {
ActivityService.execStatus(activityId,currenStatus,Status.Pass);
return new Result("0000","活动审批完成");
}

@Override
public Result checkRefuse(String activityId, Enum<Status> currenStatus) {
ActivityService.execStatus(activityId,currenStatus,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","活动审批撤销回到编辑中");
}

@Override
public Result close(String activityId, Enum<Status> currentStatus) {
ActivityService.execStatus(activityId,currentStatus,Status.Editing);
return new Result("0000","活动审批关闭完成");
}

@Override
public Result open(String activityId, Enum<Status> currentStatus) {
return new Result("0001","非关闭活动不可开启");
}

@Override
public Result doing(String activityId, Enum<Status> currentStatus) {
return new Result("0001","待审批活动不可执行活动中变更");
}
}

这里提供两个具体实现类的内容–编辑状态和提审状态。

状态处理服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.bestrookie.design;
import com.bestrookie.Status;
import com.bestrookie.design.event.*;
import com.bestrookie.util.Result;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author bestrookie
* @date 2021/12/16 3:10 下午
*/
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());
}
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数据结构中。同时,提供了不同名称的接口操作类,让外部调用方可以更加容易地使用此项目功能接口。

TestApi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.bestrookie.design;
import com.alibaba.fastjson.JSON;
import com.bestrookie.ActivityService;
import com.bestrookie.Status;
import com.bestrookie.util.Result;
/**
* @author bestrookie
* @date 2021/12/16 8:07 下午
*/
public class TestApi {
public static void main(String[] args) {
String activityId = "100001";
ActivityService.init(activityId, Status.Editing);
StateHandler stateHandler = new StateHandler();
Result result = stateHandler.arraignment(activityId,Status.Editing);
System.out.println(JSON.toJSON(result));
Result result1 = stateHandler.open(activityId,Status.Editing);
System.out.println(JSON.toJSON(result1));
System.out.println(JSON.toJSON(ActivityService.queryActivityInfo(activityId).getStatus()));
}
}

总结

从以上两种方式对一个需求的实现对比可以看到,在使用设计模式处理后,已经没有了if…else,代码的结构也更加清晰,易于扩展。在实现结构的编码方式上,可以看到不再是面向过程的编程,而是面向对象的编程。并且这种设计模式满足了单一职责和开闭原则,当只有满足这种结构时,才会发现代码的扩展是容易的,也就是增加或修改功能不会影响整体。如果状态和各项流传较多,就会产生较多的实现类。因此,可能会给代码的实现增加时间成本,因为如果遇到这种场景可以按需评估投入回报率,主要在于是否会经常修改,是否可以做成组件化,抽离业务功能与非业务功能。

评论