贫瘠之地

华北无浪漫,死海扬起帆
多少个夜晚,独自望着天

0%

状态模式 - State

背景

状态模式基于有限状态机(FSM finite state machine)的概念,主要思想是程序在任意时刻一定处于 N 种有限数量的状态中,并且在特定状态中执行特定状态的操作并可以流转至下一状态(也可不变)

这些数量有限且预先定义的状态切换规则被称为 状态流转

目的 将状态以及状态行为等属性和业务对象解耦,是一种基于组合模式进行逻辑委托的行为模式

现实世界类比 地铁电动闸门会根据当前状态和用户行为进行不同的操作并且流转为下一状态:

  • 关闭状态
    • 用户刷卡成功 -> 开门 & 流转至开启状态
    • 用户刷卡失败 -> 提示刷卡失败
  • 开启状态
    • 用户刷卡 -> 提示刷卡无效
    • 等待 -> 流转至开启关闭状态

实践

模拟一个场景,需要对审批工单进行相应操作

问题

一开始对于工单审批的状态定义比较简单

  1. 用户填写工单,状态初始为 [草稿]
  2. 用户提交工单,工单状态进入 [待审核],向管理员发送邮件通知
  3. 管理员审核工单,工单状态进入 [通过],向用户发送邮件通知
  4. 管理员驳回工单,工单状态进入 [驳回],向用户发送邮件通知
  5. [驳回] 的工单用户可以再次提交,进入 [待审核],向管理员发送邮件通知
  6. 用户执行工单,工单状态进入 [已执行]

后续随着对于工单审核流程的迭代,需要定义更多的状态以及行为

例如需要将工单状态的 [已执行] 拆分为 [执行成功] 和 [执行失败],执行失败会向用户和管理员发送执行失败的结果邮件通知,并且失败的工单可以进行再次执行

整个状态的流转规则在核心业务代码中往往使用 if...else 语句实现,再加上迭代越来越多的状态以及行为,会导致判断语句愈发臃肿

实现

首先可以定义出一个行为目录:

  • 提交
  • 通过
  • 驳回
  • 执行

接下来结合行为和状态就可以定义出如下的状态表

当前状态 | 流转状态 草稿 待审核 驳回 通过 已执行
草稿 / 提交 / / /
待审核 / / 驳回 审核 /
驳回 / 提交 / / /
通过 / / / / 执行
已执行 / / / / /

状态枚举

定义出工单的各种状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Getter
public enum WorkOrderState {

DRAFT(0, "草稿"),
WAIT_REVIEW(0, "待审核"),
REVIEWED(0, "通过"),
REJECTED(0, "驳回"),
DONE(0, "已执行"),
;

WorkOrderState(final int code, final String desc) {
this.code = code;
this.desc = desc;
}

private final int code;

private final String desc;

}

工单业务实体

工单信息的业务实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Data
public class WorkOrder {

private Integer id;

private String title;

private String optionContent;

private WorkOrderState state;

private Integer userId;

}

抽象状态处理器

抽象的状态处理器,默认实现为不支持相应操作,抛出 IllegalStateException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public abstract class AbstreactStateHandler {

protected void submit(WorkOrder workOrder) {
this.IllegalState(workOrder, "submit");
}

protected void pass(WorkOrder workOrder) {
this.IllegalState(workOrder, "pass");
}

protected void reject(WorkOrder workOrder) {
this.IllegalState(workOrder, "reject");
}

protected void run(WorkOrder workOrder) {
this.IllegalState(workOrder, "run");
}

private void IllegalState(WorkOrder workOrder, String option) {
throw new IllegalStateException(String
.format("不支持的操作 ID:[%s] 当前状态:[%s] 操作:[%s]", workOrder.getId(), workOrder.getState().getDesc(), option));
}

}

状态处理器实现

对抽象处理器相应的方法进行实现

草稿状态

1
2
3
4
5
6
7
8
9
class DraftStateHandler extends AbstreactStateHandler {

@Override
protected void submit(WorkOrder workOrder) {
System.out.println(
String.format("工单状态 [%s] -> [%s]", workOrder.getState().getDesc(), WorkOrderState.WAIT_REVIEW.getDesc()));
System.out.println("向管理员发送需要审核邮件通知");
}
}

待审核状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class WaitReviewStateHandler extends AbstreactStateHandler {

@Override
protected void pass(WorkOrder workOrder) {
System.out.println(
String.format("工单状态 [%s] -> [%s]", workOrder.getState().getDesc(), WorkOrderState.REVIEWED.getDesc()));
System.out.println("向用户发送审核通过邮件通知");
}

@Override
protected void reject(WorkOrder workOrder) {
System.out.println(
String.format("工单状态 [%s] -> [%s]", workOrder.getState().getDesc(), WorkOrderState.REJECTED.getDesc()));
System.out.println("向用户发送审核驳回邮件通知");
}
}

通过状态

1
2
3
4
5
6
7
8
class ReviewedStateHandler extends AbstreactStateHandler {

@Override
protected void run(WorkOrder workOrder) {
System.out
.println(String.format("工单状态 [%s] -> [%s]", workOrder.getState().getDesc(), WorkOrderState.DONE.getDesc()));
}
}

其他的实现类比即可

状态处理器管理员

为什么会有 “状态处理器管理员” 的角色

事实上如果是面向对象的思想,会有所区别:

  • 状态和状态的行为应该是一体的,在本例中拆分成了枚举类和状态处理器
  • 状态流转在某些程序中应该是连续的,但是在 Web 开发中每次请求都是无状态的,所以需要管理员的角色根据当前业务状态选择合适的处理器进行处理
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
public class StateHandlerHolder {

private static Map<WorkOrderState, AbstreactStateHandler> HANDLERS;

static {
HANDLERS = new HashMap<>();
HANDLERS.put(WorkOrderState.DRAFT, new DraftStateHandler());
HANDLERS.put(WorkOrderState.WAIT_REVIEW, new WaitReviewStateHandler());
HANDLERS.put(WorkOrderState.REVIEWED, new ReviewedStateHandler());
HANDLERS.put(WorkOrderState.REJECTED, new RejectedStateHandler());
HANDLERS.put(WorkOrderState.DONE, new DoneStateHandler());
}

public void submit(WorkOrder workOrder) {
HANDLERS.get(workOrder.getState()).submit(workOrder);
}

public void pass(WorkOrder workOrder) {
HANDLERS.get(workOrder.getState()).pass(workOrder);
}

public void reject(WorkOrder workOrder) {
HANDLERS.get(workOrder.getState()).reject(workOrder);
}

public void run(WorkOrder workOrder) {
HANDLERS.get(workOrder.getState()).run(workOrder);
}

}

执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Run {

public static void main(String[] args) {
WorkOrder workOrder = new WorkOrder();
workOrder.setId(1);
workOrder.setState(WorkOrderState.DRAFT);

StateHandlerHolder holder = new StateHandlerHolder();
holder.submit(workOrder);

workOrder.setState(WorkOrderState.WAIT_REVIEW);
holder.pass(workOrder);

holder.submit(workOrder);
}
}
1
2
3
4
5
6
7
工单状态 [草稿] -> [待审核]
向管理员发送需要审核邮件通知

工单状态 [待审核] -> [通过]
向用户发送审核通过邮件通知

Exception in thread "main" java.lang.IllegalStateException: 不支持的操作 ID:[1] 当前状态:[待审核] 操作:[submit]

扩展新状态

将工单状态的 [已执行] 拆分为 [执行成功] 和 [执行失败],执行失败会向用户和管理员发送执行失败的结果邮件通知,并且失败的工单可以进行再次执行

可以看出扩展了状态,并且没有扩展行为,那么可以按照以下步骤进行实现:

  1. 增加新的状态枚举对象
  2. 修改已审核状态下执行操作的实现
  3. 进行新状态处理器实现
  4. 注册至状态处理器管理员中

已审核状态处理器实现修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ReviewedStateHandler extends AbstreactStateHandler {

@Override
protected void run(WorkOrder workOrder) {
boolean success = RandomUtil.randomInt(0, 100) > 50;
if (success) {
System.out.println(
String.format("工单状态 [%s] -> [%s]", workOrder.getState().getDesc(), WorkOrderState.SUCCESS.getDesc()));
} else {
System.out.println(
String.format("工单状态 [%s] -> [%s]", workOrder.getState().getDesc(), WorkOrderState.FAILED.getDesc()));
}
}
}

新的状态处理器实现

1
2
3
4
5
6
7
8
9
10
11
class SuccessStateHandler extends AbstreactStateHandler {
}

class FailedStateHandler extends AbstreactStateHandler {
@Override
protected void submit(final WorkOrder workOrder) {
System.out.println(
String.format("工单状态 [%s] -> [%s]", workOrder.getState().getDesc(), WorkOrderState.WAIT_REVIEW.getDesc()));
System.out.println("向管理员发送需要审核邮件通知");
}
}

执行

1
2
3
4
5
6
7
8
9
10
11
public class Run {

public static void main(String[] args) {
WorkOrder workOrder = new WorkOrder();
workOrder.setId(1);
workOrder.setState(WorkOrderState.FAILED);

StateHandlerHolder holder = new StateHandlerHolder();
holder.submit(workOrder);
}
}
1
2
工单状态 [执行失败] -> [待审核]
向管理员发送需要审核邮件通知

总结

状态模式是行为模式的一种

  • 优点
    • 可以在独立于其他状态的情况下添加新状态或修改已有状态, 减少维护成本
    • 清理掉核心代码中大量的分支判断
    • 状态处理的实现可以结构化,可以实现模板方法、复用公共代码
  • 缺点
    • 状态不复杂的业务没必要过度设计
    • 需要在策略中定义所有行为(虽然可以使用基类进行默认实现),即定义出一个规则表

状态模式和策略模式

状态模式和策略模式都基于组合来进行实现,都是委派业务逻辑给其他实现类来达到解耦的目的

策略模式使得这些实现类对象相互之间完全独立, 它们不知道其他对象的存在

但状态模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态

参考

状态设计模式 (refactoringguru.cn)https://www.kancloud.cn/digest/xing-designpattern/143730)