定义
状态模式(Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.)翻译过来就是:允许一个对象在其内部状态改变时改变其行为,这个对象看起来好像是改变了其类。状态模式是一种对象行为型模式。
组成角色
状态模式的通用类图如下:
状态模式包含角色如下:
- 上下文角色(Context):上下文角色一般是一个类,上下文角色会聚合很多和 state,这些 state 使用静态常量修饰,并且负责 state 的状态切换;另外上下文角色还会包含抽象状态角色中定义的所有行为如 request,然后内部将请求委托给 state 的 handle 处理;
- 抽象状态角色(State):抽象状态角色一般是一个抽象类,用来定义具体状态的公共行为比如 handle,任何具体状态都必须实现该抽象类中的抽象方法;
- 具体状态角色(ConcreteState):继承抽象状态角色,实现抽象方法,实际处理来自 Context 的委托请求,当 Context 改变状态时行为也跟着改变。
状态模式代码实现
状态模式的代码实现如下:
// 抽象状态角色
abstract class State {
// 上下文角色,负责状态切换
protected Ctx context;
public void setContext(Ctx context) {
this.context = context;
}
// 状态的公共行为,需要子类自行实现其状态对应的行为
abstract void handle1();
abstract void handle2();
}
// 具体状态1
class ConcreteState1 extends State {
@Override
void handle1() {
// 本状态ConcreteState1时的业务逻辑
}
@Override
void handle2() {
// 设置当前状态为state2
super.context.setCurrentState(Ctx.state2);
super.context.request2();
}
}
// 具体状态2
class ConcreteState2 extends State {
@Override
void handle1() {
super.context.setCurrentState(Ctx.state1);
super.context.request1();
}
@Override
void handle2() {
// 本状态ConcreteState2时的业务逻辑
}
}
class Ctx {
// 上下文角色一般会包含全部状态,使用静态常量修饰
public final static State state1 = new ConcreteState1();
public final static State state2 = new ConcreteState2();
// 定义上下文保存的当前状态
private State currentState;
public State getCurrentState() {
return currentState;
}
// 设置当前状态
public void setCurrentState(State currentState) {
this.currentState = currentState;
// 初始化state中的上下文
this.currentState.setContext(this);
}
// 上下文一般包含抽象状态中的所有行为,然后委托给state
public void request1() {
this.currentState.handle1();
}
public void request2() {
this.currentState.handle2();
}
}
测试方法一般如下:
Ctx context = new Ctx(); // 新建一个上下文
context.setCurrentState(new ConcreteState1()); // 设置当前状态
context.request1(); // 调用request动作
context.request2();
优缺点
状态模式的优缺点总结如下:
- 减少代码体积,利于拓展:状态模式可以消除繁杂的条件判断语句块,使得业务逻辑清晰,很好地应对对象状态的增加、删除的业务场景,因为添加新的状态只需要增加新的状态类就好了;
- 状态模式状态很多时会导致状态类比较多,子类太多的时候就不方便维护管理了。
应用场景
状态模式的应用场景如下:
- 行为随状态改变而改变的场景;
- 化繁为简,如果代码中包含大量的条件语句块比如 switch…case、if 等,这些语句块的出现会导致业务逻辑变更时代码块也会变更,对状态的增加、删除时的调整修改起来比较吃力时就可以考虑状态模式;
使用实例
不引入状态模式时
状态模式的重点在于状态切换,往往一个对象的内部状态发生变化时,该对象的具体行为也会发生改变,开起来就像对象的状态在控制着行为的变化一样。比如我们家里熟悉的电视机,其状态可以分为待机、关机以及正常播放三种状态,各个状态下对应的行为用下表描述如下
开机 | 关机 | 播放 | 待机 | |
---|---|---|---|---|
待机状态 | √ | √ | ||
关机状态 | √ | √ | ||
播放状态 | √ | √ |
如上表格,我们将电视机的状态以及各个状态可以进行的行为罗列了下(其中√代表可以进行的行为,空白代表不可以进行或者进行了无效果的行为),假设我们有一个电视机对象,当然首先是定义一个电视机的接口:
// 定义一个电视机接口
interface ITelevision {
// 开机
void powerOn();
// 关机
void powerOff();
// 播放
void play();
// 待机
void standby();
}
接下来就是我们的电视机的实现类:
// 电视机的实现类
class Telev implements ITelevision {
@Override
public void powerOn() {
System.out.println("开机...");
}
@Override
public void powerOff() {
System.out.println("关机...");
}
@Override
public void play() {
System.out.println("播放...");
}
@Override
public void standby() {
System.out.println("待机...");
}
}
没错,这里我们只是简单的实现了下接口中的各个行为,我们在 main 方法中模拟电视机进行下测试:
public static void main(String[] args) {
ITelevision tv = new Telev();
tv.powerOn();
tv.play();
tv.standby();
tv.powerOff();
}
运行效果相比大家都知道了吧,上述测试输出如下:
开机...
播放...
待机...
关机...
但是我们一开始也说了,电视机的各个行为是在其状态约束下才有的,比如待机行为,你总不能在电视机正处于关机状态直接按遥控器待机吧。这里我们将上面的代码进行下改造,在电视机中加入其状态,以便我们的行为可以根据状态进行一些操作,首先定义一个表示电视机对象状态的枚举 TVStateEnum:
// 定义一个电视机状态的枚举
enum TVStateEnum {
// 分别定义待机、关机、播放三种状态
STANDBY_STATE(1), POWER_OFF_STATE(2), PLAY_STATE(3);
private final int state;
private TVStateEnum(int state) {
this.state = state;
}
}
接下来我们在电视机对象中引入电视机状态的概念,同时修改我们的行为逻辑,在内部加入状态判断,修改下电视机类如下:
// 电视机的实现类
class Telev implements ITelevision {
// 这里加入电视机的状态字段,构造中传入
private TVStateEnum state;
public Telev(TVStateEnum state) {
this.state = state;
}
public TVStateEnum getState() {
return state;
}
public void setState(TVStateEnum state) {
this.state = state;
}
// 开机
@Override
public void powerOn() {
switch (this.state) {
// 待机状态
case STANDBY_STATE:
// 待机状态进行开机,没有任何效果,所以这里什么也不做,以下同理
break;
// 关机状态
case POWER_OFF_STATE:
// 关机状态进行开机,是允许的,开机之后默认属于standby待机状态
System.out.println("开机...");
this.setState(TVStateEnum.STANDBY_STATE);
break;
// 播放状态
case PLAY_STATE:
// 播放状态进行开机,没有任何效果,所以这里什么也不做,以下同理
break;
default:
break;
}
}
// 关机
@Override
public void powerOff() {
switch (this.state) {
// 待机状态 & 播放状态 都可以进行关机操作
case STANDBY_STATE:
case PLAY_STATE:
System.out.println("关机...");
this.setState(TVStateEnum.POWER_OFF_STATE);
break;
// 关机状态
case POWER_OFF_STATE:
break;
default:
break;
}
}
// 播放
@Override
public void play() {
switch (this.state) {
// 待机状态
case STANDBY_STATE:
System.out.println("播放...");
this.setState(TVStateEnum.PLAY_STATE);
break;
// 关机状态
case POWER_OFF_STATE:
break;
// 播放状态
case PLAY_STATE:
break;
default:
break;
}
}
// 待机
@Override
public void standby() {
switch (this.state) {
// 待机状态
case STANDBY_STATE:
break;
// 关机状态
case POWER_OFF_STATE:
System.out.println("关机...");
this.setState(TVStateEnum.POWER_OFF_STATE);
break;
// 播放状态
case PLAY_STATE:
System.out.println("待机...");
this.setState(TVStateEnum.STANDBY_STATE);
break;
default:
break;
}
}
}
上面的电视机的行为中,我们加入了电视机状态的判断,使用了冗长的 switch…case 语句,目的就是控制电视机对象的行为不至于脱离其状态而随意执行,当然测试代码也要同步修改下
public static void main(String[] args) {
ITelevision tv = new Telev(TVStateEnum.POWER_OFF_STATE);
tv.play(); // 如果直接进行播放的话,因为电视机处于待机状态,所以没有任何输出
// 必须先开机,才能播放
tv.powerOn();
tv.play();
tv.standby();
tv.powerOff();
}
这里需要注意的是,加入了状态判断之后,如果直接调用 play 的话是不被允许的,因为电视机的默认状态是关机,上述测试输出如下:
开机...
播放...
待机...
关机...
引入状态模式
在上面的电视机例子中,我们发现,行为操作前需要进行各种状态判断,而这些判断使用了比较冗余的 switch…case 语句来实现的,假设我们后面电视机的状态不止如上三种了,比如加入了死机状态,那么我们的行为都要对该状态做出处理(尽管死机状态下我们的操作行为是无效的,case 语句中不必做任何响应,但这不代表我们可以省略 case 判断逻辑)。
状态模式的出现就刚好可以解决冗余的 switch…case 逻辑,就好比之前我们讲工厂方法模式的出现解决了简单工厂模式中的冗余的 if 判断一样,避免了巨大的条件语句块的出现,了解过规则引擎的朋友应该都听说过 Drools,规则引擎的出现也是为了解决冗余多变的业务逻辑判断问题,从这一角度来讲,状态模式也是如此。下面一起看下引入了状态模式之后我们的类图设计:
电视机的抽象状态:
// 抽象的电视机状态角色
abstract class TVState {
// 使用遥控器作为上下文,控制电视机状态的切换
protected RemoteControlMachine remoteControlMachine;
public void setRemoteControlMachine(RemoteControlMachine remoteControlMachine) {
this.remoteControlMachine = remoteControlMachine;
}
// 开机
abstract void powerOn();
// 关机
abstract void powerOff();
// 播放
abstract void play();
// 待机
abstract void standby();
}
待机状态:
// 待机状态
class StandByState extends TVState {
@Override
void powerOn() {
// do nothing
}
@Override
void powerOff() {
System.out.println("关机...");
// 使用遥控器设置电视机状态为 关机
super.remoteControlMachine.setCurrentState(RemoteControlMachine.POWER_OFF_STATE);
// 执行关机的行为
super.remoteControlMachine.powerOff();
}
@Override
void play() {
System.out.println("播放...");
super.remoteControlMachine.setCurrentState(RemoteControlMachine.PLAY_STATE);
// 执行播放的行为
super.remoteControlMachine.play();
}
@Override
void standby() {
// do nothing
}
}
关机状态:
// 关机状态
class PowerOffState extends TVState {
@Override
void powerOn() {
System.out.println("开机...");
// 开机后状态默认为 待机
super.remoteControlMachine.setCurrentState(RemoteControlMachine.STANDBY_STATE);
// 执行待机的行为
super.remoteControlMachine.standby();
}
@Override
void powerOff() {
// do nothing
}
@Override
void play() {
// do nothing
}
@Override
void standby() {
// do nothing
}
}
播放状态:
// 播放状态
class PlayState extends TVState {
@Override
void powerOn() {
// do nothing
}
@Override
void powerOff() {
System.out.println("关机...");
// 使用遥控器设置电视机状态为 关机
super.remoteControlMachine.setCurrentState(RemoteControlMachine.POWER_OFF_STATE);
// 执行关机的行为
super.remoteControlMachine.powerOff();
}
@Override
void play() {
// do nothing
}
@Override
void standby() {
System.out.println("待机...");
// 使用遥控器设置电视机状态为 待机
super.remoteControlMachine.setCurrentState(RemoteControlMachine.STANDBY_STATE);
// 执行待机的行为
super.remoteControlMachine.standby();
}
遥控器角色(上下文角色)
// 遥控器,扮演上下文角色,负责电视机状态切换
class RemoteControlMachine {
// 包含电视机的三种状态:待机、关机、播放
public final static TVState STANDBY_STATE = new StandByState();
public final static TVState POWER_OFF_STATE = new PowerOffState();
public final static TVState PLAY_STATE = new PlayState();
// 标识当前状态
private TVState currentState;
// 获取当前状态
public TVState getCurrentState() {
return currentState;
}
// 设置当前状态,遥控器负责电视机的具体状态切换
public void setCurrentState(TVState currentState) {
this.currentState = currentState;
this.currentState.setRemoteControlMachine(this);
}
// 委托给state统一去处理
public void powerOn() {
// 当前状态下如何powerOn,由state去确定
this.currentState.powerOn();
}
public void powerOff() {
this.currentState.powerOff();
}
public void play() {
this.currentState.play();
}
public void standby() {
this.currentState.standby();
}
}
测试
RemoteControlMachine context = new RemoteControlMachine();
context.setCurrentState(new PowerOffState());
context.play(); // 如果直接进行播放的话,因为电视机处于待机状态,所以没有任何输出
context.powerOn();
context.play();
context.standby();
context.powerOff();
上面测试输出如下:
开机...
播放...
待机...
关机...
可以看到,测试结果没有任何不同,但是我们没有写一行 switch…case 语句块,反而是将对象的各个状态抽出来做成状态类,然后各个状态类在对各个行为做出实现,代码更加精简。
状态模式具体的状态类在对状态做出变更时其行为也跟着做出变更,其实代码量减少并不十分明显,但是对于状态拓展十分友好,只需要增加状态类再实现各个行为即可拓展新的状态出来,也体现了开闭原则及单一职责原则;状态模式将对象状态的变更放到类的内部进行,外部调用者无需关心对象的状态及行为的变化,也体现了更好的封装性;另外对代码的 cpd(代码重复率检测)也是很有提升明显。
总结
本小节我们介绍了状态模式的定义,优缺点已经使用场景,然后用电视机的例子帮大家更好地理解模式,状态模式的出现,一定程度解决了繁杂的语句块的硬编码的形式,成为条件分支、判断的终结者,另外状态模式下代码结构更加清晰,面向拓展更加友好。