状态设计模式的实践
小轲

简介

状态模式是一种行为设计模式, 让你能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。

image

其主要思想是程序在任意时刻仅可处于几种有限的状态中。 在任何一个特定状态中, 程序的行为都不相同, 且可瞬间从一个状态切换到另一个状态。 不过, 根据当前状态, 程序可能会切换到另外一种状态, 也可能会保持当前状态不变。 这些数量有限且预先定义的状态切换规则被称为转移。

你还可将该方法应用在对象上。 假如你有一个计算任务调度服务平台。 任务可能会处于 创建启动已完成等多种状态, 任务运行策略会在不同状态下的行为略有不同:

  • 处于 启动时, 它会将文档转移到完成/失败中状态
  • 处于 重启时, 它会将文档转移到启动状态
  • 处于 停止时,它会转移到熔断状态

image

编码阶段(demo code)

下面是一个简单的类图,我们以此来进行推进demo。

image

步骤 1

创建一个接口。

1
2
3
public interface State {
public void doAction(Context context);
}

步骤 2

创建实现接口的实体类。

StartState.java

1
2
3
4
5
6
7
8
9
10
11
public class StartState implements State {

public void doAction(Context context) {
System.out.println("Player is in start state");
context.setState(this);
}

public String toString(){
return "Start State";
}
}

StopState.java:

1
2
3
4
5
6
7
8
9
10
11
public class StopState implements State {

public void doAction(Context context) {
System.out.println("Player is in stop state");
context.setState(this);
}

public String toString(){
return "Stop State";
}
}

步骤 3

创建 Context 类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Context {
private State state;

public Context(){
state = null;
}

public void setState(State state){
this.state = state;
}

public State getState(){
return state;
}
}

步骤 4

使用 Context 来查看当状态 State 改变时的行为变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class StatePatternDemo {
public static void main(String[] args) {
Context context = new Context();

StartState startState = new StartState();
startState.doAction(context);

System.out.println(context.getState().toString());

StopState stopState = new StopState();
stopState.doAction(context);

System.out.println(context.getState().toString());
}
}

步骤 5

执行程序,输出结果:

1
2
3
4
Player is in start state
Start State
Player is in stop state
Stop State

上面这些code已经完成了,有一些问题,下一步准备优化

Spring优化版本

上面的demo阶段有一些问题

  1. 无法融入到spring容器中
  2. 假如加入到spring容器中,涉及到的类都需要进行一遍注入?
1
2
3
4
5
@Resource
private StartState startState;

@Resource
private StopState stopState;

问题也在此暴露了出来,无法真正的实现解耦

  1. 无法做熔断,可能会出现过多的try-catch,比如执行start失败后需要处理熔断,执行stop后需要处理重试

改进思路

状态设计模式适用于多种状态判断时,进行各状态解耦的前提下,还得注意保护自己的代码

  1. 首先并不推荐使用Spring的状态机,个人理解对代码的侵入性太强
  2. 简洁的change方式更能体现出业务代码,切划分更细
  3. 增加一个枚举,来进行参数的传递
  4. 所有的bean全部加入到spring容器中,做单例管理
  5. 增加一个类似于适配器的选择器,传递枚举、context来继续实现状态模式
    1. context需要改造,不应持有状态的对象。应该持有变量来进行规范

下面是改进后的类图

image

增加枚举,确定状态传递参数

枚举用来作为参数的传递,是本次改造重要的一环

1
2
3
4
5
6
7
8
@Getter
@AllArgsConstructor
public enum StateEnum {
START(1),
STOP(2),
RE_START(3);
private int state;
}

Context类进行改造

context改造后,仅作为参数类进行传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

/**
* 上下文
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Context {

/**
* 用户ID
*/
private Long userId;
/**
* current state,当前状态
*/
private StateEnum stateEnum;
}

state类融入spring容器,改造

先把相关类加入到容器中

1
2
3
4
5
6
7
8
9
10
@Service
public class StartState implements State {
//code
}

@Service
public class StopState implements State {
//code
}

State类增加hit函数,判定是否命中

给State类增加一个是否命中函数,相关实现类指定自己的枚举值进行eq

1
2
3
4
5
6
7
8
9
10
public interface State {

/**
* 判断是否命中
* @param context 上下文对象
* @return true则代表命中
*/
boolean isHit(Context context);

}

下面再给各个实现类增加实现

StartState:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class StartState implements State {

/**
* 判断是否命中
* @param context 上下文对象
* @return true则代表命中
*/
@Override
public boolean isHit(Context context) {
return StateEnum.START.equals(context.getStateEnum());
}
}

StopState:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class StopState implements State {

/**
* 判断是否命中
* @param context 上下文对象
* @return true则代表命中
*/
@Override
public boolean isHit(Context context) {
return StateEnum.STOP.equals(context.getStateEnum());
}
}

增加一个选择器

比较重要的一环出现了,每个类都进行了改造。该标记自己的标记自己,该增加参数的增加参数,那么怎么用起来这些参数?也就是刚刚所说的适配器

  1. 注入State下的所有字类用来匹配
  2. 循环使用isHit函数来进行匹配,匹配到后invoke相关函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class Change {

@Autowired
private List<State> stateList;

public void doAction(Context context){
for (State state : stateList) {
if (state.isHit(context)) {
state.doAction(context);
}
}
}
}

做个测试

Change对象就作为所有State类的入口来进行匹配了,只需要更改相关的参数类(Context),即可调度相关的状态实现

1
2
3
4
5
6
7
8
9
10
11
12
    @Autowired
private Change change;

@Test
public void testState(){
Context context;
context=new Context(1L, StateEnum.START);
change.doAction(context);
context = new Context(1L, StateEnum.STOP);
change.doAction(context);
}
}

参考

  1. 状态模式 | 菜鸟教程
  2. 状态设计模式
 评论
评论插件加载失败
正在加载评论插件