什么是状态模式(State)
概念
状态模式(State Pattern)属于行为型模式,定义:一个对象的行为由其内部的状态决定。
要想学习状态模式,那就一定要提到状态机了,状态机又是什么?状态机就是列举出一个东西的多种状态,研究这几种状态的切换行为,然后绘制成一个可以连通的状态图。
这里以门闸为例,在广州地铁里,我们过完安检后,想要进入到地铁内部的话就需要刷卡过门闸,这里的闸门就有很多种状态,未刷卡前是关闭状态,我们无法通过闸门;刷卡后会有一个中间状态:处理中状态,这个状态,我们已经刷了卡,但机器还在处理,没有把门打开,我们也无法通过;处理完成后就变成了打开状态,我们可以通过,通过后就变成了关闭状态。在这个过程中,闸门有三种状态:关闭、处理中、打开,我们需要跟闸门进行交互的行为:刷卡、通过,下面我画一个状态图:
我在学习的时候发现,这个状态模式的实现跟策略模式的几乎一毛一样。
可以说状态模式是策略模式的衍生版,虽然他们的实现是一模一样的,但是解决的问题却是不一样的。策略模式是把算法封装起来,让这些算法可以相互替换,提高代码的复用性;而状态模式完完全全就是围绕着状态机来的,提供了一个状态机的实现方式,状态模式才能说减少了因判断当前状态的if-else和switch的使用,后面会做一下演示。
优点
- 状态对象封装了状态的转换规则,减少了判断语句的使用。
- 不同的环境对象可以共用状态对象,减少了对象的个数。
- 符合单一职责原则。分离出不同的状态和行为,让每个状态类的职责更加清晰,有利于系统的扩展。
缺点
- 没有遵守开放封闭原则。如果新增了新的状态,想要使用这个状态,就要在旧的状态类中加上转换到新状态的逻辑。
- 设计与实现比较复杂。
原则
“+”代表遵守,“-”代表不遵守或者不相关
原则 | 开放封闭 | 单一职责 | 迪米特 | 里氏替换 | 依赖倒置 | 接口隔离 | 合成复用 |
---|---|---|---|---|---|---|---|
- | + | - | - | + | - | - | |
适用场景
- 当一个对象的行为取决于它的状态。
- 需要大量判断或分支语句的情况。
如何实现
想要实现状态模式,需要以下三样东西:
- 环境抽象类(Abstract Context):它需要定义与用户交互的方法,并且有一个状态对象,所有的方法都委托给这个状态对象来实现
- 环境(Context)类:实现环境抽象类,并且扩展其他功能。
- 状态(State)接口/抽象类:定义状态的行为,这些行为和环境类的一致,还需要实现状态切换的逻辑。
- 具体状态(Concrete State)类:实现抽象状态接口/抽象类,实现状态转换逻辑。
类图
例子
这里以前面的闸门为例,闸门的三种状态:关闭、处理中、打开,跟闸门进行交互的行为:刷卡、通过。
不用状态模式的例子
这里简单写一下不用状态模式实现会是怎么样的:
/**
* 不使用状态模式实现的闸门
* Created on 2021/6/15.
*
* @author xuxiaobai
*/
public class NoUserStatePatternGate {
//当前状态,初始状态为关闭状态
private String currentState=CLOSE_STATE;
//关闭状态
private static String CLOSE_STATE="closed";
//打开状态
private static String OPEN_STATE="open";
//处理中
private static String PROCESSING="processing";
/**
* 刷卡
*/
public void pay(){
if (this.currentState.equals(CLOSE_STATE)){
//当前是关闭,刷卡应该转为处理中
System.out.println("刷卡成功,正在处理");
//转为正在处理状态
this.currentState=PROCESSING;
Random random=new Random();
//模拟出现故障
if (random.nextBoolean()){
processSuccess();
}else {
processFail();
}
return;
}
if (this.currentState.equals(PROCESSING)){
System.out.println("请稍等。。正在处理");
return;
}
if (this.currentState.equals(OPEN_STATE)){
System.out.println("闸门已经打开了,请通过");
return;
}
}
/**
* 通过
*/
public void pass(){
if (this.currentState.equals(CLOSE_STATE)){
System.out.println("无法通过,请刷卡");
return;
}
if (this.currentState.equals(PROCESSING)){
System.out.println("请稍等。。正在处理");
return;
}
if (this.currentState.equals(OPEN_STATE)){
System.out.println("已通过,闸门关闭");
this.currentState=CLOSE_STATE;
return;
}
}
/**
* 处理失败
*/
private void processFail(){
System.out.println("处理失败!");
//转回关闭状态
this.currentState=CLOSE_STATE;
}
/**
* 处理成功
*/
private void processSuccess(){
System.out.println("处理成功!");
//转到打开状态
this.currentState=OPEN_STATE;
}
}
测试:
/**
* 状态模式测试
* Created on 2021/6/15.
*
* @author xuxiaobai
*/
public class StateTest {
public static void main(String[] args) {
NoUserStatePatternGate gate=new NoUserStatePatternGate();
gate.pass();
gate.pay();
gate.pay();
gate.pass();
gate.pay();
gate.pass();
gate.pay();
gate.pass();
gate.pay();
gate.pass();
/**
* 结果:
* 无法通过,请刷卡
* 刷卡成功,正在处理
* 处理成功!
* 闸门已经打开了,请通过
* 已通过,闸门关闭
* 刷卡成功,正在处理
* 处理成功!
* 已通过,闸门关闭
* 刷卡成功,正在处理
* 处理失败!
* 无法通过,请刷卡
* 刷卡成功,正在处理
* 处理失败!
* 无法通过,请刷卡
*/
}
}
基本功能实现了,减去了支付扣钱这些操作,如果你想实现的话,可以拷贝下去补充。
这里的if-else不是一般的多啊,改成switch会好一点,不过还是会让整个代码变得非常的麻烦,如果以后想要再加新增状态,就只能继续加if-else了,逻辑也非常的混乱,多加几个状态后,可能就不是普通人所能驾驭的了,下面来看看使用状态模式是如何实现的。
![](https://i-blog.csdnimg.cn/blog_migrate/89e3699617763ea9618580d252832057.jpeg)
使用状态模式
代码有那么一点复杂,加了一个共享状态对象,其他的就是把这些判断当前状态的if-else去掉了,每个if-else的逻辑操作都分散到状态类中了。
类图
这个类图有那么一丢丢乱,没办法,状态模式实现起来有点复杂,不过每个类的职责都很清晰的,结合后面的代码来看。
环境类
环境抽象类
/**
* 环境抽象类
* 闸门抽象类
*
* Created on 2021/6/15.
*
* @author xuxiaobai
*/
public abstract class AbstractGate {
/**
* 闸门状态
*/
protected AbstractGateState state;
/**
* 因为这一部分的代码都是一样的
* 所以这里写到抽象方法中了
*/
/**
* 刷卡
*/
public void pay() {
this.state.pay(this);
}
/**
* 通过
*/
public void pass() {
this.state.pass(this);
}
public AbstractGateState getState() {
return state;
}
protected AbstractGate setState(AbstractGateState state) {
this.state = state;
return this;
}
}
具体环境类
/**
* 环境类
* 地铁闸门
* Created on 2021/6/15.
*
* @author xuxiaobai
*/
public class SubwayGate extends AbstractGate{
public SubwayGate(){
//默认关闭
this(false);
}
public SubwayGate(boolean isOpen){
//可以通过构件方法设置是否开启闸门
Class stateClass;
if (isOpen){
stateClass= OpenState.class;
}else {
stateClass=ClosedState.class;
}
this.state=AbstractGateState.getState(stateClass);
}
}
状态类
状态抽象类
/**
* 状态抽象类
* 闸门状态
* Created on 2021/6/15.
*
* @author xuxiaobai
*/
public abstract class AbstractGateState {
private static Map<Class,AbstractGateState> stateMap=new HashMap<>();
/**
* 刷卡
*/
public abstract void pay(AbstractGate gate);
/**
* 通过
*/
public abstract void pass(AbstractGate gate);
/**
* 这里写一个空方法
* 主要是考虑到处理状态是一个中间的瞬时的状态,需要完成一定的处理逻辑
* 如果只是切换状态的话,就无法触发处理逻辑,环境类只与用户交互两个动作:pay、pass,无法触发“处理中状态”的处理逻辑
* @throws IllegalAccessException
*/
public void handle(AbstractGate gate) throws IllegalAccessException {
throw new IllegalAccessException();
}
/**
* 用这个方法来获取状态对象,实现状态对象的共用
* @param stateClass
* @return
*/
protected static final AbstractGateState getState(Class stateClass){
AbstractGateState state = stateMap.get(stateClass);
//双重校验
if (state==null){
synchronized (AbstractGateState.class){
state = stateMap.get(stateClass);
if (state==null){
try {
state= (AbstractGateState) stateClass.newInstance();
stateMap.put(stateClass,state);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
return state;
}
}
关闭状态
/**
* 具体状态类
* 关闭状态
* Created on 2021/6/15.
*
* @author xuxiaobai
*/
public class ClosedState extends AbstractGateState {
@Override
public void pay(AbstractGate gate) {
//当前是关闭,刷卡应该转为处理中
System.out.println("刷卡成功,正在处理");
//设置为处理中状态
AbstractGateState processingState = getState(ProcessingState.class);
gate.setState(processingState);
try {
processingState.handle(gate);
} catch (IllegalAccessException e) {
}
}
@Override
public void pass(AbstractGate gate) {
System.out.println("无法通过,请刷卡");
}
}
处理中状态
/**
* 具体状态类
* 处理中状态
* Created on 2021/6/15.
*
* @author xuxiaobai
*/
public class ProcessingState extends AbstractGateState {
//模拟出现故障概率
private Random random = new Random();
@Override
public void pay(AbstractGate gate) {
System.out.println("请稍等。。正在处理");
return;
}
@Override
public void pass(AbstractGate gate) {
System.out.println("请稍等。。正在处理");
return;
}
@Override
public void handle(AbstractGate gate) throws IllegalAccessException {
//转为正在处理状态
if (random.nextBoolean()) {
System.out.println("处理失败!");
gate.setState(getState(ClosedState.class));
} else {
System.out.println("处理成功!");
gate.setState(getState(OpenState.class));
}
}
}
打开状态
/**
*
* 具体状态类
* 打开状态
* Created on 2021/6/15.
*
* @author xuxiaobai
*/
public class OpenState extends AbstractGateState {
@Override
public void pay(AbstractGate gate) {
System.out.println("闸门已经打开了,请通过");
return;
}
@Override
public void pass(AbstractGate gate) {
System.out.println("已通过,闸门关闭");
//设置为关闭状态
gate.setState(getState(ClosedState.class));
return;
}
}
测试
/**
* 状态模式测试
* Created on 2021/6/15.
*
* @author xuxiaobai
*/
public class StateTest {
public static void main(String[] args) {
AbstractGate gate=new SubwayGate();
gate.pass();
gate.pay();
gate.pay();
gate.pay();
gate.pay();
gate.pass();
gate.pay();
gate.pass();
gate.pay();
gate.pass();
gate.pay();
gate.pass();
/**
* 结果:
* 无法通过,请刷卡
* 刷卡成功,正在处理
* 处理成功!
* 闸门已经打开了,请通过
* 已通过,闸门关闭
* 刷卡成功,正在处理
* 处理成功!
* 已通过,闸门关闭
* 刷卡成功,正在处理
* 处理失败!
* 无法通过,请刷卡
* 刷卡成功,正在处理
* 处理失败!
* 无法通过,请刷卡
*/
}
}
这里的处理是否通过是有概率的,所以每次结果都会不一样。
写到这里就结束了,你都看到这了,还不给我点个赞?
总结
虽说状态模式是策略模式的衍生版,但它们在使用上不会存在太大的差别,唯一需要注意的就是它们俩的思维逻辑不同,策略模式只关注把算法、逻辑等等封装到一个类中,而状态模式是把算法与逻辑和状态一起封装到一个类中,状态模式是离不开状态的,可以说状态模式包含策略模式。