概述
状态模式一种行为型模式。在状态模式中,允许对象在内部状态发生改变的同时也改变它的行为。
以电视遥控器为例:
- 当电视电源关闭时,遥控器检测到当前是「关机状态」,点击遥控器中的按钮时会亮红灯,不发射任何遥控信号。
- 而当电视电源开启时,遥控器检测到当前是「开机状态」,点击遥控器中的按钮时会亮绿灯,并发射按钮对应的遥控信号。
一看到需求,先风骚地创建一个含有 PowerFlag (电视电源开关标识) 实例变量的 TvController 类来表示遥控器,在接收到按钮事件后通过判断 powerFlag 标识来决定是否发射对应信号,比如:
public class TvController {// ...private boolean mPowerFlag;public void onNextChannel() {if (mPowerFlag) {System.out.println("亮绿灯,并切换到下一个频道");return;}System.out.println("仅红灯");}}
这样做在逻辑上当然没有问题,因为我们只有一个 PowerFlag 标识呀,只需判断当前电视是否开机来决定是否发射信号就好了。但在实际生活中,遥控器的功能可能没那么简单噢,比如说:
- 电视除了有电源开关状态外,还有一个是否待机的状态,预期在待机状态下遥控器是不能发射遥控信号的。
- 另外,遥控器是有电池的吧,当电池快没电时,预期点击按钮发射信号时不再亮绿灯,而是亮黄灯,以提示用户电源不足。
- …
如果我们要实现的遥控器这么复杂,就需要在 onNextChannel() 等事件通知方法中写大量 if - else 判断逻辑,这无疑增加了系统的复杂性。
那么,有没有一种好的设计模式可用来处理这种场景呢?
有,那便是状态模式。下面我们不再通过 if - else 的方式来编写遥控器代码了,大家都是成年人,编码姿势要优雅一点。
优雅的编码姿势

- 电视的电源开关状态可以被遥控器检测到。
- 遥控器内部的有一个抽象出来的行为接口,根据电视电源开关状态有着不一样的具体实现,如:「开机时状态对象」和「关机时状态对象 (按钮点击发射信号)」。
- 点击遥控器中的按钮时,遥控器会通过当前的状态对象执行定义好的逻辑。
代码如下:
// 抽象出遥控器的行为 (状态)public interface TvState {void onNextChannel();void onPrevChannel();void onTurnUp();void onTurnDown();}// 当电视电源为开启时的状态实现public class PowerOnState implements TvState {@Overridepublic void onNextChannel() {System.out.println("* 亮绿灯,切换到下一个频道");}@Overridepublic void onPrevChannel() {System.out.println("* 亮绿灯,切换到上一个频道");}@Overridepublic void onTurnUp() {System.out.println("* 亮绿灯,调大声音");}@Overridepublic void onTurnDown() {System.out.println("* 亮绿灯,调小声音");}}// 当电视电源为关闭时的状态实现public class PowerOffState implements TvState {@Overridepublic void onNextChannel() {System.out.println("* 仅亮红灯");}@Overridepublic void onPrevChannel() {System.out.println("* 仅亮红灯");}@Overridepublic void onTurnUp() {System.out.println("* 仅亮红灯");}@Overridepublic void onTurnDown() {System.out.println("* 仅亮红灯");}}
// 电源开或关的行为 (通知)public interface PowerController {void powerOn();void powerOff();}// 遥控器类 (可接收电源开关的通知、可处理按钮的点击事件)public class TvController implements PowerController, TvState {private TvState mState;public TvController() {mState = new PowerOffState();}@Overridepublic void powerOn() {System.out.println("- 检测到电视已开机");mState = new PowerOnState();}@Overridepublic void powerOff() {System.out.println("- 检测到电视已关机");mState = new PowerOffState();}@Overridepublic void onNextChannel() {mState.onNextChannel();}@Overridepublic void onPrevChannel() {mState.onPrevChannel();}@Overridepublic void onTurnUp() {mState.onTurnUp();}@Overridepublic void onTurnDown() {mState.onTurnDown();}}
在上述遥控器的相关代码中:
- 当检测到电视电源状态发生变化时,遥控器会设置对应的具体状态对象。
- 当接收到按钮点击事件时,直接使用当前最新的状态对象执行对应方法即可,不用再写繁琐的
if - else判断了。
当然是真的,不信我们来验证一下:
public static void main(String[] args) {// 创建遥控对象时,发现电视正处于关机状态TvController controller = new TvController();// 电视开机之前,点击遥控的控制按钮都没有用controller.onNextChannel();controller.onTurnUp();System.out.println("点击电视上的电源按钮");controller.powerOn();controller.onNextChannel();controller.onPrevChannel();controller.onTurnUp();controller.onTurnDown();System.out.println("再次点击电视上的电源按钮");controller.powerOff();// 电视关机之后,点击遥控的控制按钮都没有用controller.onNextChannel();controller.onTurnUp();controller.onTurnDown();}
日志输出如下:
* 仅亮红灯* 仅亮红灯点击电视上的电源按钮- 检测到电视已开机* 亮绿灯,切换到下一个频道* 亮绿灯,切换到上一个频道* 亮绿灯,调大声音* 亮绿灯,调小声音再次点击电视上的电源按钮- 检测到电视已关机* 仅亮红灯* 仅亮红灯* 仅亮红灯
由输出日志可知,改善后的遥控器代码的运行逻辑是完全符合预期的。
总结
当代码中包含大量与对象状态有关的逻辑判断时,可以将这些逻辑相关的行为抽象出来,并作不同的状态实现。当状态发生变化时,只需根据最新状态设置不同的对象即可。
优点:
- 封装了转换规则。
- 枚举可能的状态,在枚举状态之前已经确定好了状态种类。
- 将所有与某个状态有关的行为放到一个类中,可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是写一长串儿条件语句。
- 可以让多个环境共享一个状态对象,从而减少系统中对象的个数。
缺点:
- 状态模式的使用必然会增加系统类和对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
- 状态模式对 “开闭原则” 的支持并不太好,增加新的状态类时需要修改那些负责状态转换的代码逻辑,否则无法切换到新增状态。
