定义:
允许一个对象在其内部状态改变时改变它的行为,这个对象看起来就像改变了它的类一样。
状态模式的含义是将对象的状态封装成一个独立的类,并将动作行为委托到代表当前状态的对象,行为会随着内部的状态而改变。状态模式是通过状态对象来改变动作行为,而不同的状态对象里不同的动作行为表现会不一样,这使得它看起来就像在改变它的类一样。
设计类图:
状态模式中的角色:
- Context上下文环境角色,负责状态的切换,持有一个内部状态对象,代表着环境当前所处的状态。
- State抽象状态角色, 可以是一个接口或者抽象类,定义了所有具体状态的共同接口或者是说动作行为,任何具体状态都要实现这些接口。
- ConcreteState具体状态角色,处理来自Context的请求,每一个ConcreteState都提供了它自己对请求的实现,它要完成两个任务,一个是本状态下的行为管理,另一个是如何过渡到下一个状态。并且持有环境角色以实现状态的切换。
public interface State {
//行为1
public void handle1();
//行为2
public void handle2();
}
public class ConcreteStateA implements State {
private Context context;
public ConcreteStateA(Context context) {
this.context = context;
}
@Override
public void handle1() {
//当前状态下handle1行为的处理
//...
}
@Override
public void handle2() {
//当前状态下handle2行为的处理
//...
this.context.setCurrentSate(Context.STATE_B);
}
}
public class ConcreteStateB implements State {
private Context context;
public ConcreteStateB(Context context) {
this.context = context;
}
@Override
public void handle1() {
//当前状态下handle1行为的处理
//...
}
@Override
public void handle2() {
//当前状态下handle2行为的处理
//...
this.context.setCurrentSate(Context.STATE_A);
}
}
public class Context {
public static State STATE_A;
public static State STATE_B;
private State currentSate;
public Context() {
STATE_A = new ConcreteStateA(this);
STATE_B = new ConcreteStateB(this);
}
public void setCurrentSate(State currentSate) {
this.currentSate = currentSate;
}
public void handle1(){
this.currentSate.handle1();
}
public void handle2(){
this.currentSate.handle2();
}
}
public class Client {
public static void main(String[] args) {
//创建环境对象
Context context = new Context();
//初始化状态
context.setCurrentSate(new ConcreteStateA(context));
//执行动作
context.handle1();
context.handle2();
}
}
巴士公交车的例子
如果经常乘坐巴士或公交车的朋友可能比较熟悉,公交车有几个状态,开门、关门、行驶、停车,对应状态下的行为是有一些规则的,比如公交车在行驶过程中是不允许开门不能上人的,只有在停止状态下并且开门状态下才能上人,并且必须是关门状态下才能够启动开始行驶。
下面的表格中,我列举了巴士的所有状态和行为:
状态 | 开门 | 关门 | 启动 | 停止 | 上人 | 下人 |
---|---|---|---|---|---|---|
开门停止状态 | N | Y | N | N | Y | Y |
开门运行状态 | N | Y | N | Y | N | N |
关门停止状态 | Y | N | Y | N | N | N |
关门运行状态 | N | N | N | Y | N | N |
先来看一下,不用状态模式的情况下,也就是我们通常的做法,是如何写代码来处理这些情况的:
//巴士类
public class Bus {
/**
* 开车门
*/
public void openDoor() {
}
/**
* 关车门
*/
public void closeDoor() {
}
/**
* 巴士开始行驶
*/
public void run() {
}
/**
* 停止巴士
*/
public void stop() {
}
/**
* 乘客上车
*/
public void pickUpPassenger() {
}
/**
* 乘客下车
*/
public void dropOffPassenger() {
}
}
public class Client {
static String state;
public static void main(String[] args) {
Bus bus = new Bus();
if (state.equals("开门停止")) {
bus.pickUpPassenger();
bus.dropOffPassenger();
bus.closeDoor();
} else if (state.equals("开门运行")) {
bus.closeDoor();
bus.stop();
} else if (state.equals("关门停止")) {
bus.openDoor();
bus.run();
} else if (state.equals("关门运行")) {
bus.stop();
}
}
}
没错,你可能看到了熟悉的代码:很多的if-else条件判断语句,不管这里的state变量是字符串还是整型的常量,相信你平时写代码时一定写过类似的代码,而且有的时候判断的情况非常之多,或者是你是用switch-case来处理,都是类似的情况。
下面来看一下如何用状态模式来处理这些情况,我们前面讲状态模式是将动作行为委托到状态类,因此我们的思路就转变为首先去建立相关的状态类。
实现代码:
/**
* 巴士抽象状态类
*/
public abstract class BusState {
/**持有Bus的引用*/
protected Bus bus;
public BusState(Bus bus) {
this.bus = bus;
}
/**
* 开车门
*/
public abstract void openDoor();
/**
* 关车门
*/
public abstract void closeDoor();
/**
* 巴士开始行驶
*/
public abstract void run();
/**
* 停止巴士
*/
public abstract void stop();
/**
* 乘客上车
*/
public abstract void pickUpPassenger();
/**
* 乘客下车
*/
public abstract void dropOffPassenger();
}
/**
* 开门停止状态
*/
public class OpenedStoppedState extends BusState {
public OpenedStoppedState(Bus bus) {
super(bus);
}
@Override
public void openDoor() {
//啥都不做,因为已经是开门状态下了不用再开门了
}
@Override
public void closeDoor() {
System.out.println("巴士关门了");
//关门之后,巴士应该变成关门停止状态
this.bus.setCurrentState(Bus.CLOSED_STOPPED_STATE);
}
@Override
public void run() {
throw new UnsupportedOperationException("开门停止状态下禁止行驶");
}
@Override
public void stop() {
//啥都不做,因为已经是停止状态下了不用再停止了
}
@Override
public void pickUpPassenger() {
System.out.println("乘客上车");
}
@Override
public void dropOffPassenger() {
System.out.println("乘客下车");
}
}
/**
* 开门运行状态
*/
public class OpenedRunningState extends BusState {
public OpenedRunningState(Bus bus) {
super(bus);
}
@Override
public void openDoor() {
//do nothing 已经是开门状态了,实际上这也是一个非法行为你也可以抛出异常
}
@Override
public void closeDoor() {
System.out.println("巴士关门了");
//关门后,巴士变成关门行驶状态
this.bus.setCurrentState(Bus.CLOSED_RUNNING_STATE);
}
@Override
public void run() {
//do nothing 已经是运行状态了
}
@Override
public void stop() {
System.out.println("巴士停车了");
//停车后,巴士变成开门停车状态
this.bus.setCurrentState(Bus.OPENED_STOPPED_STATE);
}
@Override
public void pickUpPassenger() {
throw new UnsupportedOperationException("开门运行状态下禁止乘客上车");
}
@Override
public void dropOffPassenger() {
throw new UnsupportedOperationException("开门运行状态下禁止乘客下车");
}
}
/**
* 关门停止状态
*/
public class ClosedStoppedState extends BusState {
public ClosedStoppedState(Bus bus) {
super(bus);
}
@Override
public void openDoor() {
System.out.println("巴士开门了");
//开门后,巴士变成开门停止状态
this.bus.setCurrentState(Bus.OPENED_STOPPED_STATE);
}
@Override
public void closeDoor() {
//do nothing 已经是关门状态了
}
@Override
public void run() {
System.out.println("巴士开始行驶");
//行驶后,巴士变成关门运行状态
this.bus.setCurrentState(Bus.CLOSED_RUNNING_STATE);
}
@Override
public void stop() {
//do nothing 已经是停止状态了
}
@Override
public void pickUpPassenger() {
throw new UnsupportedOperationException("关门停止状态禁止乘客上车");
}
@Override
public void dropOffPassenger() {
throw new UnsupportedOperationException("关门停止状态禁止乘客下车");
}
}
/**
* 关门运行状态
*/
public class ClosedRunningState extends BusState {
public ClosedRunningState(Bus bus) {
super(bus);
}
@Override
public void openDoor() {
throw new UnsupportedOperationException("关门运行状态禁止开门");
}
@Override
public void closeDoor() {
//do nothing 已经是关门状态了
}
@Override
public void run() {
//do nothing 已经是运行状态了
}
@Override
public void stop() {
System.out.println("巴士停车了");
//停车后,巴士变成关门停止状态
this.bus.setCurrentState(Bus.CLOSED_STOPPED_STATE);
}
@Override
public void pickUpPassenger() {
throw new UnsupportedOperationException("关门运行状态禁止乘客上车");
}
@Override
public void dropOffPassenger() {
throw new UnsupportedOperationException("关门运行状态禁止乘客下车");
}
}
public class Bus {
/**分别代表四种状态*/
public static BusState OPENED_STOPPED_STATE;
public static BusState OPENED_RUNNING_STATE;
public static BusState CLOSED_STOPPED_STATE;
public static BusState CLOSED_RUNNING_STATE;
/**当前状态*/
private BusState mCurrentState;
public Bus() {
OPENED_STOPPED_STATE = new OpenedStoppedState(this);
OPENED_RUNNING_STATE = new OpenedRunningState(this);
CLOSED_STOPPED_STATE = new ClosedStoppedState(this);
CLOSED_RUNNING_STATE = new ClosedRunningState(this);
}
/**
* 设置当前的状态
*/
public void setCurrentState(BusState state) {
mCurrentState = state;
}
/**
* 开车门
*/
public void openDoor() {
mCurrentState.openDoor();
}
/**
* 关车门
*/
public void closeDoor() {
mCurrentState.closeDoor();
}
/**
* 巴士开始行驶
*/
public void run() {
mCurrentState.run();
}
/**
* 停止巴士
*/
public void stop() {
mCurrentState.stop();
}
/**
* 乘客上车
*/
public void pickUpPassenger() {
mCurrentState.pickUpPassenger();
}
/**
* 乘客下车
*/
public void dropOffPassenger() {
mCurrentState.dropOffPassenger();
}
}
public class Client {
public static void main(String[] args) {
Bus bus = new Bus();
//设置初始状态为关门运行状态
BusState initState = new ClosedRunningState(bus);
bus.setCurrentState(initState);
//测试正常情况:停车->开门->下车->上车->关门->行驶
bus.stop();
bus.openDoor();
bus.dropOffPassenger();
bus.pickUpPassenger();
bus.closeDoor();
bus.run();
//测试非正常情况1:关门行驶的时候去开门
bus.setCurrentState(initState);
bus.openDoor();
//测试非正常情况2:门未关上就开始行驶
bus.setCurrentState(new OpenedStoppedState(bus));
bus.run();
}
}
测试正常情况输出:
测试非正常情况1输出:
测试非正常情况2输出:
可以看到在状态模式实现的代码中,将以往的if-else条件判断转换为了四个相关的状态类来处理,客户端在使用的时候,无需关心当前是哪个状态,它只要执行具体的行为就可以,至于这些行为是否可以在当前状态下得到正确的响应完全由当前的状态类内部去处理。每一个状态类都拥有Context的所有的行为接口方法。在某一个状态类的内部实现中,我们看到,如果一个行为可以执行,那么就实现该行为的代码,如果一个行为在这个状态下是非法的或者禁止的,那么就抛出异常或者你什么也不做。
理解状态和行为
状态就是当前环境所处的情况或者是场景,在不同场景下当前环境对于不同行为要给出不同的响应,具体在代码中的体现可以理解为就是你的if-else判断。而行为就是你要执行的动作,你要做的事情,你要给出的响应。例如人冷了要加衣服,热了要脱衣服,冷热就是状态,而加衣脱衣就是行为。
如图,状态模式下做的事情就是,由以往的Context上下文环境自己来挑选判断执行哪种行为,变成了由对应的状态类去挑选和执行哪种行为。状态类会对每一种行为都做出响应,这些响应可以是执行动作的响应,也可以是一种非法提醒的反馈响应,也可以什么都不做,这些要根据你的具体的业务来判断。对于环境来说,它的动作执行流程从表面上看是流畅的不被打断的(不被if-else的结构所破坏)。
状态模式的优点
- 结构清晰,避免了过多的 switch- case 或者 if- else 语句的使用,避免了程序的复杂性,提高系统的可维护性。
- 封装性强,遵循设计原则,很好地体现了开闭原则和单一职责原则,每个状态都有一个子类负责,你要增加状态就要增加子类,你要修改状态,你只修改一个子类就可以了。另外也体现了迪米特法则,状态变换完全放到类的内部来实现,屏蔽了客户端调用的时候对状态处理的感知。
状态模式的缺点
缺点很明显,那就是子类会太多,因为每一个状态都对应一个子类,所以当你的状态非常多的时候就会发生类膨胀。
状态模式和策略模式的区别
状态模式和策略模式有着相同的类图, 但是它们的意图不同。策略模式更加侧重于行为或算法的替换,并且可以在运行时动态的任意替换,侧重点是使用不同算法族来解决问题。而状态模式更加侧重于状态的改变影响改变行为,而行为的执行结果又会导致状态发生变化,是一个状态和行为的变化过程。策略模式需要客户端必须完全知晓所有的策略方法,才能够知道究竟哪一个策略是当前需要的。而状态模式客户端在使用的时候,可以不必关心有哪些状态,他只需要去调用环境的行为就可以了,在环境的内部维护了这些状态。
状态模式的使用场景
- 行为随着状态的改变而改变的时候,这也是状态模式的根本出发点,例如权限设计,人员的状态不同即使执行相同的行为结果也会不同,在这种情况下需要考虑使用状态模式。
- 条件、分支判断语句的替代者,在程序中大量使用 switch 语句或者if-else判断语句会导致程序结构不清晰,逻辑混乱,使用状态模式可以很好地避免这一问题。
虽然状态模式可以很好的处理行为受状态约束的情况,但相应的对象的状态也会增加,所以在实际项目中使用的时候,要权衡利弊,考虑它对你的设计带来的后果影响是否可以接受。如果状态太多比如十几个或者几十个,还是建议不要使用了。
参考: