前言
今天我们来看一个号称策略模式双胞胎的设计模式——状态模式,如它的名字一样,状态模式最核心的设计思路就是将对象的状态抽象出一个接口,然后根据它的不同状态封装其行为,这样就可以实现状态和行为的绑定,最终实现对象和状态的有效解耦。下面我们就来详细看下它的基本原理和实现过程吧。
状态模式
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
要点
- 状态模式允许一个对象基于内部状态而拥有不同的行为
- 和程序状态机(
PSM
)不同,状态模式用类代表状态 Context
会将行为委托给当前状态对象- 通过将每个状态封装进一个类,我们把以后需要做的任何改变局部化了
- 状态模式和策略模式有相同的类图,但是它们的意图不同
- 策略模式通常会用行为或算法来配置
Context
类 - 状态模式允许
Context
随着状态的改变而改变行为 - 状态转换可以由
State
类或Context
类控制 - 使用状态模式通常会导致设计中类的数目大量增加
- 状态类可以被多个
Context
示例共享
优缺点
优点
-
封装了转换规则。
-
枚举可能的状态,在枚举状态之前需要确定状态种类。
-
将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
-
允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块
-
可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点
- 状态模式的使用必然会增加系统类和对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
- 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
使用场景
- 行为随状态改变而改变的场景。
- 条件、分支语句的代替者。
示例
状态接口
首先是状态接口,这个接口是给我们实际的状态对象继承的,这个接口有一个方法doAction
,这个方法就是给不同的状态对象实现的,用于处理不同状态下的行为的。
public interface State {
/**
* 改变状态的操作
* @param context
*/
void doAction(Context context);
}
状态所属者
然后是我们的状态所属者,这个类有一个核心的属性就是我们的State
接口。
public class Context {
private State state;
public Context(){}
public void setState(State state){
this.state = state;
}
public State getState(){
return state;
}
@Override
public String toString() {
return "Context{" +
"state=" + state +
'}';
}
}
状态实现
状态实现者继承了State
接口,并实现了doAction
方法,在方法内部可以对我们的状态所有者进行对应的操作。
这里是一个启动状态:
public class StopState implements State {
private String name;
public StopState() {
this.name = "stop";
}
@Override
public void doAction(Context context) {
System.out.println("Context is in stop state");
context.setState(this);
System.out.println(context);
}
@Override
public String toString() {
return "StopState{" +
"name='" + name + '\'' +
'}';
}
}
这里是停止状态
public class StartState implements State{
private String name;
public StartState() {
this.name = "start";
}
@Override
public void doAction(Context context) {
System.out.println("Context is in start state");
context.setState(this);
System.out.println(context);
}
@Override
public String toString() {
return "StartState{" +
"name='" + name + '\'' +
'}';
}
}
测试代码
这里分别实例化了容器和状态的示例,然后通过示例的doAction
方法操作容器
@Test
public void testState() {
Context context = new Context();
StartState startState = new StartState();
startState.doAction(context);
StopState stopState = new StopState();
stopState.doAction(context);
}
运行结果
可以看到,状态对象的doAction
方法执行后,容器对应的状态也发生了改变:
好了,关于状态模式就先说这么多,接下来我们做一个简单的总结。
总结
有用过策略模式或者对策略模式比较熟悉的小伙伴应该发现了:策略模式其实和我们今天分析状态模式特别像,甚至连架构模式都是一样的,所以这里我们有必要说下它们的区别。
首先是策略模式,它其实是将不同的算法封装成不同的策略,然后在具体的策略中实现具体的行为,但是测试本身是被动被选择的,容器选择策略,调用过程发生在容器中,而且策略本身是入参;
而我们今天分析的状态模式,它是将不同状态对应的行为封装,然后由具体的状态操作容器,整个过程更像是状态主动发起的,由状态执行其自己的方法,入参是容器。
这两种设计模式从某种程度上说是可以互相替换的,但是还是要结合具体业务分析的,比如spring boot
启动过程中,它用到的就是状态模式,这一点我们在分析spring boot
启动过程中也发现了;但如果是涉及到算法层面的内容,比如两个数的加减乘除,显然策略模式才是更好的选择。
总之,学习设计模式除了要了解它的基本原理和应用场景之外,更重要的是,要学会辨识优秀框架中的设计模式(知识,知就是知道,了解,识局势辨识,分析),最终将这些设计模式应用到我们的业务开发之中。