前言
状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。当系统中的某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式。
正文
状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化。对应客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致处理。
一、定义
定义如下:
状态模式: 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
State Pattern: Allow an object to alter its behavior when its internal state changes.The object will
appear to change its class.
状态模式又名状态对象(Objects for States),它是一种对象行为型模式。
二、结构
状态模式包含了3个角色:
(1)Context(环境类):环境类又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象类State的实例,这个实例定义当前状态,在具体实现时它是一个State子类的对象。
(2)State(抽象状态类):它用于定义一个接口以封装与环境类的一个特定状态相关的 行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现了这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
(3)ConcreteState(具体状态类):它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类对应环境的一个具体状态,不同的具体状态类的行为有所不同。
三、实现
抽象状态类,典型代码如下:
public abstract class State{
//声明抽象业务方法,不同的具体状态类可以有不同的实现
public abstract void handle();
}
典型的具体状态类代码如下:
public class ConcreteState extends State{
public void handle(){
//方法的具体实现代码
}
}
环境类典型代码如下:
public class Context{
private State state; //维持一个对抽象状态对象的引用
private int value; //其他属性值,该属性值的变化可能会导致对象的状态发生变化
//设置状态对象
public void setState(State state){
this.state = state;
}
public void request(){
//其他代码
state.handle(); //调用状态对象的业务方法
//其他代码
}
}
在实际使用时,环境类Context和抽象状态类State之间可能存在更为复杂的关系,State与Context之间可能也存在依赖或者双向关联关系。
一个对象的状态之间可以进行相互转换,通常有两种方式:
(1)统一由环境类来负责状态之间的转换,此时环境类还充当了状态管理器(StateManager)角色。代码片段如下:
...
public void changeState(){
//判断属性值,根据属性值进行状态转换
if(value == 0){
this.setState(new ConcreteStateA());
}
else if(value == 1){
this.setState(new ConcreteSateB());
}
...
}
(2)由具体状态类来负责状态之间的转换,可以在具体状态类的业务方法中判断环境类的某些属性值,再根据情况为环境类设置新的状态对象,实现状态转换。同样,也可以提供一个专门的方法来负责属性值的判断和状态转换。代码片段如下:
...
public void changeState(Context ctx){
//根据环境对象中的属性值进行状态转换
if(ctx.getValue() == 1){
ctx.setState(new ConcreteStateB());
}
else if(ctx.getValue() == 2){
ctx.setState(new ConcreteStateC());
}
...
}
四、共享状态
有些情况下,多个环境对象可能需要共享同一个状态,如果希望在系统中实现多个环境对象共享一个或多个状态对象,那么需要将这些状态对象定义为环境类的静态成员对象。
五、使用环境类实现状态转换
在状态模式中实现状态转换时,具体状态类可通过调用环境类Context的setState()方法进行状态的转换操作,也可以统一由环境类Cotext来实现状态的转换。此时增加新的具体状态类可能需要修改其他具体状态类或者环境类的源代码,否则系统无法转换到新增状态。但是对于客户端来说无须关心状态类,可以为环境类设置默认的状态类,而将状态的转换工作交给具体状态类或环境类来完成,具体的转换细节对于客户端而言是透明的。
六、状态模式优缺点与适用环境
优点
(1)状态模式封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
(2)状态模式将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。
(3)状态模式允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以避免使用庞大的条件语句将业务方法和状态转换代码交织在一起。
(4)状态模式可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点
(1)状态模式会增加系统中类和对象的个数,导致系统运行开销增大。
(2)状态模式的结构和实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度。
(3)状态模式对开闭原则的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需要修改对应类的源代码。
适用环境
(1)对象的行为依赖于它的状态(例如某些属性值),状态的改变将导致行为的变化。
(2)在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强。
以上文字,大量摘抄自《Java设计模式》一书,由刘伟老师编著,故本文应当列入转载一类,有兴趣的朋友可以直接阅读原书。