对象的状态由各个属性的当前值构成当我们调用某个对象的setXXX()方法时通常表示修改它的XXX属性另外对象在执行方法时也可能修改自己的状态在某些情形下例如建立事务或机器模型时对象的状态可能是决定其行为的关键因素依赖于状态的代码逻辑可能遍布于类的大量方法State模式的目标就是简化这类代码把依赖于状态的逻辑集中到一组类每一个类代表一种不同的状态避免if语句嵌套过深或过于复杂转而依赖于多态性来调用不同的方法
状态模型
如果对象的状态信息很关键对象会拥有一些变量来指示如何根据状态做出相应的动作这些变量大量地散布于复杂的多层嵌套if语句中来描述对象如何响应可能出现的事件用这种方式建立对象模型的最大缺点在于if语句可能变得相当复杂一旦要修改对象的状态模型往往有多个方法的许多if语句需要调整
以传送带的门为例考虑其状态变化过程为传送带的门由单个按钮控制并且假设初始时处于关闭状态按一下按钮门开始打开如果在门完全打开之前再次按下按钮门开始关闭一旦门完全打开它将在秒延时之后自动开始关闭过程要禁止门自动关闭可以在门打开之后按一下按钮图描述了传送门的状态变化情况它是一个UML状态机(State Machine)其中click表示按下按钮的动作显然与纯文字描述相比UML状态机图示更加直观易懂
按照常规的设计思路(不使用State设计模式)在模拟传送带工作过程的软件中可以使用一个Door对象代表传送门(如图所示)状态改变事件由传送带软件发送给Door对象
图 UML状态机
图 状态改变事件发送给Door对象
Door类从Observable派生这样客户程序(例如一个GUI程序)就能够方便地了解传送门状态Door类首先定义传送门可能处于的状态代码如下
public class Door extends Observable {
public static final int CLOSED = ;
public static final int OPENING= ;
public static final int OPEN = ;
public static final int CLOSING= ;
public static final int STAYOPEN = ;
private int state = CLOSED;
//
}
status()方法返回传送门状态的文字描述如下所示
public String status() {
switch (state) {
case OPENING :
return "正在打开";
case OPEN :
//
default :
return "已关闭";
}
}
当用户点击传送带的按钮时传送带程序调用Door对象的click()方法click()方法模拟图所示的状态装换过程
public void click() {
if (state == CLOSED) {
setState(OPENING);
}
else if (state == OPENING || state == STAYOPEN) {
setState(CLOSING);
}
else if (state == OPEN) {
setState(STAYOPEN);
}
else if (state == CLOSING) {
setState(OPENING);
}
}
Door类的setState()方法向观察者通知传送门状态改变事件代码如下
private void setState(int state) {
thisstate = state;
setChanged();
notifyObservers();
}
用State模式改造
Door类的代码比较复杂整个类到处都用到了状态变量如果要比较图的状态机和Door类的各个状态变换方法将是非常困难的click()方法尤其如此那么怎样在这个例子中应用State模式呢?首先要把传送门的各种状态分别定义成类如图所示图能够更好地与图的状态机对应更改后的类设计中Door包含了状态机的上下文信息所谓上下文信息就是描述环境和一系列其它对象相关的信息就本例而言State利用一个上下文对象记录了传送门的当前状态是DoorState类的哪一个实例
图 传送门各个状态
DoorState类的构造函数要求提供一个Door对象DoorState的子类利用该对象传达状态变更信息在这种设计方案中DoorState的子类通过一个Door类型的属性绑定到特定的传送门(Door)对象因而要求一个DoorState对象只被一个Door对象引用同时Door类要把它的状态信息定义成局部变量代码如下
public class Door extends Observable {
public final DoorState CLOSED = new DoorClosed(this);
// 按照类似方式定义DoorState类型的
// OPENINGOPENCLOSINGSTAYOPEN对象(略)
private DoorState state = CLOSED;
//
}
DoorState类是一个抽象类由子类实现其click()方法在状态机中每一个状态均有相应的按下按钮操作修改后的设计中每一个描述状态的类也有一个click()方法两者是一致的DoorState类处理了其它可能的变换所以DoorState的子类可以忽略无关的事件代码如下
public abstract class DoorState {
protected Door door;
public DoorState(Door door) {
thisdoor = door;
}
public abstract void click();
public String status() {
String s = getClass()getName();
return ssubstring(slastIndexOf() + );
}
public void complete() { }
public void timeout() { }
}
由上可以看到现在的status()方法要比修改设计方案之前的status()方法简单多了新status()方法返回的结果与修改前版本的结果略有不同它的状态信息从类的名称获得如果要返回修改设计方案之前的信息只需把这些状态信息分别记录到DoorState的各个子类中然后在这个status()方法中直接提取即可
新的设计方案中传送门对象(Door)从传送带接收状态改变信息的这一角色仍未改变但现在Door对象只需把这些状态改变信息直接传递给当前的状态对象就可以了代码如下
public class Door extends Observable {
// 声明变量
protected void setState(DoorState state) {
thisstate = state;
setChanged();
notifyObservers();
}
public void click() {
stateclick();
}
// complete()status()timeout()都直接
// 调用state的相应方法即可(略)
}
这里的click()complete()status()和timeout()方法体现了Java类多态性的应用所有这些方法都起着判断和选择动作的作用即是虽然每一个方法的代码是不含if判断逻辑的但实际运行时被调用的状态对象却不断变化在调用click()时会发生哪些事情呢?按照多态性规则答案依赖于当时传送门的状态修改后的代码有效地担负起了根据状态执行不同动作的任务但由于利用了多态性它变得更加简单了
Door类中的setState()方法现在由DoorState的子类调用这些DoorState的子类与图状态机中的相应实体很相似例如状态机中Open状态包含Timeout和ClickDoorOpen类则包含两个对应的方法timeout()和click()DoorOpen类的代码如下
public class DoorOpen extends DoorState {
public DoorOpen(Door door) {
super(door);
}
public void click() {
doorsetState(doorSTAYOPEN);
}
public void timeout() {
doorsetState(doorCLOSING);
}
}
从上面可以看到利用State设计模式之后代码变得更简单了不过细心的读者或许已经注意到Door类用到的常量实际上是变量这给人一种不规范的感觉假设现在要把这些状态常量移到_DoorConstant接口这就需要从DoorState类消除Door实例变量修改办法是重新定义DoorState类中的click()complete()和timeout()变换方法把一个Door对象以参数的形式传递给它们按照这种设计方法Door对象调用状态变换方法例如click()时将采用stateclick(this)的形式