状态模式(State Pattern)
概念:状态模式 允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类
在软件设计中,我们经常会遇需要编写有很多状态的程序。最简单的如乘坐电梯程序,当我们要坐电梯时需要判断电梯的状态,只有当电梯处于当前楼时我们才能乘坐,当电梯不在当前楼层时我们要按下按钮等待电梯到来。在平时一般都通过 if…else 或者 switch 判断状态后处理,这种固定的写法只有在软件后期不会更新时才可以(不过是不可能的),状态模式其实是使用组合通过简单引用不同的状态 对象 来造成状态改变的假象。
组成:
Context(上下文):一般是一个拥有多个状态的类,当调用 request() 函数时会被委托到状态对象。
State(状态接口):定义了所有具体状态的共同接口,任何状态都实现这个接口,这样就能在状态之间相互转换。
ConcreteStateA(具体状态):实现状态接口,处理来自 Context 的请求。每个 ConcreteState 都提供自己请求的实现。
例子:
我们实现电梯的例子,电梯的状态包括运行、停止、开门、关门。当电梯运行时我们等待电梯停止,停止时我们等待开门或者运行,开门时进入后关门,进入后电梯变为关门状态。
假设目前就这么多状态,用传统的 if…else 和 switch 实现。
抽象状态类
public abstract class LiftState {
//4种状态
final int OPEN_STATE = 1;
final int STOP_STATE = 2;
final int CLOSE_STATE = 3;
final int RUN_STATE = 4;
public abstract void setState(int state);
//开门动作
public abstract void open();
//关门动作
public abstract void close();
//运行动作
public abstract void run();
//停止动作
public abstract void stop();
}
public class Lift extends LiftState{
private int state;
public Lift(int state) {
this.state = state;
}
@Override
public void setState(int state) {
this.state = state;
}
//开门动作
@Override
public void open() {
switch (state) {
case OPEN_STATE:
System.out.println("处于开门状态:什么也不做");
break;
case CLOSE_STATE:
System.out.println("处于关门状态:什么也不做");
break;
case RUN_STATE:
System.out.println("处于运行状态:什么也不做");
break;
case STOP_STATE:
System.out.println("处于关门状态,开门...");
setState(OPEN_STATE);
break;
}
}
//关门动作
@Override
public void close() {
switch (state) {
case OPEN_STATE:
System.out.println("处于开门状态:关门...");
setState(CLOSE_STATE);
break;
case CLOSE_STATE:
System.out.println("处于关门状态:什么也不做");
break;
case RUN_STATE:
System.out.println("处于运行状态:什么也不做");
break;
case STOP_STATE:
System.out.println("处于停止状态:什么也不做");
break;
}
}
//运行动作
@Override
public void run() {
switch (state) {
case OPEN_STATE:
System.out.println("处于开门状态:什么也不做");
break;
case CLOSE_STATE:
System.out.println("处于关门状态:运行...");
setState(RUN_STATE);
break;
case RUN_STATE:
System.out.println("处于运行状态:什么也不做");
break;
case STOP_STATE:
System.out.println("处于停止状态:运行...");
setState(RUN_STATE);
}
}
//停止动作
@Override
public void stop() {
switch (state) {
case OPEN_STATE:
System.out.println("处于开门状态:什么也不做");
break;
case CLOSE_STATE:
System.out.println("处于关门状态:什么也不做");
setState(CLOSE_STATE);
break;
case RUN_STATE:
System.out.println("处于运行状态:停止...");
break;
case STOP_STATE:
System.out.println("处于停止状态:什么也不做");
}
}
}
public class LiftRun {
public static void main(String[] args) {
//初始设置为停止状态
Lift lift = new Lift(2);
//停止变为运行
lift.run();
//运行时不能开门
lift.open();
//运行变为停止
lift.stop();
//停止时不能开门
lift.close();
}
}
上面的实现没什么问题,但大量的用了 switch…case,当我们想要为电梯增加一种状态时,会发现需要大量的改动代码,每一个 switch 都要改,这也是《重构》中称 switch 为代码的坏味道。我们来通过 状态模式 改变它。
状态模式改进例子:
LiftState:
public abstract class LiftState {
Lift lift;
public LiftState(Lift lift) {
this.lift = lift;
}
//开门动作
public abstract void open();
//关门动作
public abstract void close();
//运行动作
public abstract void run();
//停止动作
public abstract void stop();
}
CloseState:
public class CloseState extends LiftState {
public CloseState(Lift lift) {
super(lift);
}
@Override
public void open() {
System.out.println("处于关闭状态...什么也不做");
}
@Override
public void close() {
System.out.println("处于关闭状态...什么也不做");
}
@Override
public void run() {
System.out.println("处于关闭状态:运行");
lift.setState(lift.getRunState());
}
@Override
public void stop() {
System.out.println("处于关闭状态...什么也不做");
}
}
OpenState:
public class OpenState extends LiftState {
public OpenState(Lift lift) {
super(lift);
}
@Override
public void open() {
System.out.println("处于开门状态...什么也不错");
}
@Override
public void close() {
System.out.println("处于开门状态:关门");
lift.setState(lift.getCloseState());
}
@Override
public void run() {
System.out.println("处于开门状态...什么也不错");
}
@Override
public void stop() {
System.out.println("处于开门状态...什么也不错");
}
}
RunState:
public class RunState extends LiftState {
public RunState(Lift lift) {
super(lift);
}
@Override
public void open() {
System.out.println("处于运行状态...什么也不做");
}
@Override
public void close() {
System.out.println("处于运行状态...什么也不做");
}
@Override
public void run() {
System.out.println("处于运行状态...什么也不做");
}
@Override
public void stop() {
System.out.println("处于运行状态:停止");
lift.setState(lift.getStopState());
}
}
StopState:
public class StopState extends LiftState {
public StopState(Lift lift) {
super(lift);
}
@Override
public void open() {
System.out.println("处于停止状态:开门");
lift.setState(lift.getOpenState());
}
@Override
public void close() {
System.out.println("处于停止状态...什么也不做");
}
@Override
public void run() {
System.out.println("处于停止状态:运行");
lift.setState(lift.getRunState());
}
@Override
public void stop() {
System.out.println("处于停止状态...什么也不做");
}
}
Lift:
public class Lift {
LiftState openState;
LiftState closeState;
LiftState runState;
LiftState stopState;
LiftState state;
public Lift() {
openState = new OpenState(this);
closeState = new CloseState(this);
runState = new RunState(this);
stopState = new StopState(this);
//起始设置为停止状态
state = stopState;
}
//委托给状态对象执行
public void stop() {
state.stop();
}
public void run() {
state.run();
}
public void close() {
state.close();
}
public void open() {
state.open();
}
//仅仅更换当前对象的引用
public void setState(LiftState state) {
this.state = state;
}
public LiftState getOpenState() {
return openState;
}
public LiftState getCloseState() {
return closeState;
}
public LiftState getRunState() {
return runState;
}
public LiftState getStopState() {
return stopState;
}
}
LiftRun:
public class LiftRun {
public static void main(String[] args) {
//其实设置为停止状态
Lift lift = new Lift();
lift.run();
lift.open();
lift.stop();
lift.close();
}
}
运行结果:
通过状态模式改进后,我们将每个状态和状态行为封装成了对象和方法,虽然增加了类的数目,但是当我们增加状态时不用改动原先的代码,仅需创建新的状态实现接口的方法即可。
如增加故障状态:
public class BugState extends LiftState {
public BugState(Lift lift) {
super(lift);
}
@Override
public void open() {
System.out.println("故障状态,不能打开");
}
@Override
public void close() {
System.out.println("故障状态,不能关闭");
}
@Override
public void run() {
System.out.println("故障状态,不能运行");
}
@Override
public void stop() {
System.out.println("故障状态,停止..维修");
lift.setState(lift.getStopState());
}
@Override
public void bug() {
System.out.println("故障状态");
}
}
在每个状态对象中加入相应的 bug 状态函数,如:
@Override
public void bug() {
System.out.println("处于停止状态:维修电梯");
lift.setState(lift.getBugState());
}
接着在 Lift 类中加入 bug 状态:
LiftState bugState;
...
//构造函数中
bugState = new BugState(this);
...
public LiftState getBugState() {
return bugState;
}
测试函数:
public class LiftRun {
public static void main(String[] args) {
//其实设置为停止状态
Lift lift = new Lift();
//运行
lift.run();
lift.bug();
lift.stop();
lift.run();
}
}
处于状态模式下仅仅需要新增一个类外加做一些小改动即可完成状态的增加,大大方便了后期的维护和修改。
适用场景:
- 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。
- 代码中包含大量与对象状态有关的条件语句:一个操作中含有庞大的多分支的条件(if else 或switch case)语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常 , 有多个操作包含这一相同的条件结构。 State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
优缺点:
优点:
- 封装了转换规则,将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点:
- 状态模式的使用必然会增加系统类和对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。