1 状态模式的定义
状态模式:当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了自己的类。
状态模式的核心是封装,状态的改变引起了行为的变更,从外部看起来就好像这个对象的类发生了改变一样。
状态模式的通用类图如下:
- State——抽象状态角色
接口或抽象类,负责对象状态的定义,并且封装环境角色以实现状态切换。 - ConcreteState——具体状态角色
每一个具体状态都必须完成两个职责:(1)本状态的行为管理;(2)趋向状态处理。通俗地说,就是本状态下要做的事情,以及本状态如何过渡到其他状态。 - Context——环境角色
定义客户端需要的接口,并且负责维护具体状态的切换。
状态模式先对来说比较复杂,提供了一种对物质运动的另一个观察视角:通过状态改变促使行为的变化。
2 状态模式通用代码示例
- 抽象状态角色
抽象状态角色声明一个环境角色,提供给各状态类访问;同时提供具体状态角色的抽象行为,由各个具体状态类实现。
public abstract class State {
/**
* 定义一个环境角色,提供给子类访问
*/
protected Context context;
/**
* 设置环境角色
*/
public void setContext(Context context) {
this.context = context;
}
/**
* 定义行为A
*/
public abstract void handleA();
/**
* 定义行为B
*/
public abstract void handleB();
}
- 具体状态角色
具体状态角色有两个职责:
- 处理本状态必须完成的任务;
- 决定是否可以过渡到其他状态
STATEI:
@Slf4j
public class ConcreteStateI extends State {
@Override
public void handleA() {
log.info("本状态下必须处理的业务逻辑: {}.handleA", this.getClass().getSimpleName());
}
@Override
public void handleB() {
log.info("业务逻辑: {}.handleB", this.getClass().getSimpleName());
//设置当前状态为STATEII
super.context.setCurrentState(Context.STATEII);
//过渡到STATEII状态,由Context实现
super.context.handleB();
}
}
STATEII:
@Slf4j
public class ConcreteStateII extends State {
@Override
public void handleA() {
log.info("业务逻辑: {}.handleA", this.getClass().getSimpleName());
//设置当前状态为STATEI
super.context.setCurrentState(Context.STATEI);
//过渡到STATEI状态,由Context实现
super.context.handleA();
}
@Override
public void handleB() {
log.info("本状态下必须处理的业务逻辑: {}.handleB", this.getClass().getSimpleName());
}
}
- 环境角色
环境角色有两个习惯约定:
- 把状态对象声明为静态常量,有几个状态对象就声明几个静态常量;
- 环境角色具有状态抽象角色定义的所有行为,具体执行使用委托方式。
@Slf4j
public class Context {
/**
* 状态定义
*/
public final static State STATEI = new ConcreteStateI();
public final static State STATEII = new ConcreteStateII();
/**
* 当前状态
*/
private State currentState;
/**
* 获得当前状态
*/
public State getCurrentState() {
return this.currentState;
}
/**
* 设置当前状态
*/
public void setCurrentState(State currentState) {
this.currentState = currentState;
//切换状态
this.currentState.setContext(this);
}
/**
* 行为handleA委托
*/
public void handleA() {
this.currentState.handleA();
}
/**
* 行为handleB委托
*/
public void handleB() {
this.currentState.handleB();
}
}
- 场景类
@Slf4j
public class Client {
public static void main(String[] args) {
//定义环境角色
Context context = new Context();
//初始化状态
context.setCurrentState(Context.STATEI);
//行为执行
context.handleA();
context.handleB();
}
}
运行结果:
23:12:47.072 [main] INFO com.idear.design.pattern.state.ConcreteStateI - 本状态下必须处理的业务逻辑: ConcreteStateI.handleA
23:12:47.076 [main] INFO com.idear.design.pattern.state.ConcreteStateI - 业务逻辑: ConcreteStateI.handleB
23:12:47.076 [main] INFO com.idear.design.pattern.state.ConcreteStateII - 本状态下必须处理的业务逻辑: ConcreteStateII.handleB
状态模式隐藏了状态的变化过程,但是状态的切换引起了行为的变化。对外环境来说,只能看到行为的变化,而不用具体知道是状态变化引起的。
3 状态模式的优缺点
3.1 优点
- 结构清晰
避免使用过多的switch...case或者if...else语句,避免了程序的复杂性,提高系统的可维护性。 - 遵循设计原则
状态模式很好地体现了开闭原则和单一职责原则。每个状态都是一个子类,增加状态只需要增加子类;修改状态,只需要修改相应的子类。 - 封装性好
状态处理和状态转换在类内部实现,外部调用者不用知道类内部如何实现状态和状态变换。
3.2 缺点
状态模式有一个缺点:类膨胀——子类会太多。如果一个对象状态很多,就会有大量的子类。
4 状态模式使用场景
4.1 使用场景
- 行为随状态改变而改变的场景
例如权限设计,人员状态不同即使执行相同的行为,结果也不同。这种情况下,就可以考虑使用状态模式。 - 条件、分支判断语句的替代者
程序中大量使用switch语句或者if判断语句,会导致程序结构不清晰,逻辑混乱。这时候,可以考虑使用状态模式来避免这一问题。
4.2 注意事项
状态模式适用于当某个对象在他的状态发生改变时,它的行为也随着发生比较大的变化。也就是说在行为受状态约束的情况下可以使用状态模式,而且使用时对象的状态最好不要超过5个。