概述
状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。当系统中某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式。状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化。对于客户端而言,无需关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致性地处理。
定义
状态模式(State Pattern):允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为模式。
结构图
1.Context(环境类):环境类又称为上下文类,它拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象
2.State(抽象状态类):它用于定义一个接口以及封装与环境类的一个特定状态相关的行为,在抽象状态类中声明各种不同状态对应的方法,而在其子类中实现这些方法。由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中
3.ConcreteState(具体状态类):它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。
在状态模式中,将对象在不同状态下的行为封装到不同的状态类中,为了让系统具有更好的灵活性和可扩展性,同时对各状态下的共有行为进行封装,需要对状态进行抽象,引入了抽象状态类角色,其典型代码如下
abstract class State{
//声明抽象业务方法,不同的具体状态类可以不同的方法实现
public abstract void handle();
}
在抽象状态类的子类即具体状态类中实现了在抽象状态类中声明的业务方法,不同的具体状态类可以提供完全不同的方法实现。在实际使用时,一个状态类中可能包含多个业务方法,如果在具体状态类中某些业务方法的实现完全相同,可以将这些方法移至抽象状态类,实现代码的复用。典型的具体状态类代码如下
class ConcreteState extends State{
public void handle(){
//方法具体实现代码
}
}
环境类维持一个对抽象状态类的引用,通过setState()方法可以向环境类注入不同的状态对象,再在环境类的业务方法中调用状态对象的方法,典型代码如下
class Context{
private State state; //维持一个对抽象状态对象的引用
private int value; //其他属性值,该属性值的变化可能会导致对象状态发送变化
//设置状态对象
public void setState(State state){
this.state = state;
}
public void request(){
//其他代码
state.handle(); //调用状态对象业务方法
//其他代码
}
}
环境类实际上是真正拥有状态的对象,这里只是将环境类中与状态有关的代码提取出来封装到专门的状态类中,在状态模式结构图中,环境类Context与抽象状态类State之间存在单向关联关系,在Context中定义了一个State对象。在实际使用时,他们之间可能存在更加复杂的关系,State与Context之间可能也存在依赖或者关联关系。
在状态模式的使用过程中,一个对象的状态之间可以进行相互转换,通常有以下两种实现状态装换的方式
- 统一由环境类来负责状态之间的转换. 此时,环境类还充当了状态管理器(State Manager)角色。在环境类的业务方法中通过对某些属性值的判断实现状态转换,还可以提供一个专门的方法用于实现属性判断和状态转换,代码片段如下:
...
public void changeState(){
//判断属性值,根据属性值进行状态转换
if(value == 0){
this.setState(new ConcreteStateA());
}
else if(value == 1){
this.setState(new ConcreteStateB());
}
}
...
- **由具体状态类来负责状态之间的转换.**可以在具体状态类的业务方法中判断环境类的某些属性值再根据情况为环境类设置新的状态对象,实现状态转换;同样,也可以提供一个专门的方法来负责属性值的判断和状态转换。此时,状态类与环境类之间将存在依赖或关联关系,因为状态类需要访问环境类中的属性值,代码片段如下:
...
public void changeState(Context ctx){
//根据环境对象中的属性值进行状态转换
if(ctx.getValue() == 1){
ctx.setState(new ConcreteStateB());
}
else if(ctx.getValue() == 2){
ctx.setState(new ConcreteStateC());
}
}
实例:银行系统中的账户类设计
使用状态模式设计银行系统的账户类,客户只需要执行简单的存款和取款操作,系统会根据余额将账户自动转换到相应的状态。
结构图
在图中,Account充当环境类角色,AccountState充当抽象状态角色,NormalState,OverdraftState和RestrictedState充当具体状态角色
代码实现
银行账号:环境类
public class Account {
private AccountState state; //维持一个对抽象状态对象的引用
private String owner; //开户名
private double balance = 0; //账户余额
public Account(String owner, double init) {
this.owner = owner;
this.balance = balance;
this.state = new NormalState(this); //设置初始值
System.out.println(this.owner + "开户,初始化金额为" + init);
System.out.println("-----------------------");
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public void setState(AccountState state) {
this.state = state;
}
//存款
public void deposit(double amount) {
System.out.println(this.owner + "存款" + amount);
state.deposit(amount); //调用状态对象的deposit()方法
System.out.println("现在余额为" + this.balance);
System.out.println("现在账户状态为" + this.state.getClass().getName());
System.out.println("-----------------------");
}
//取款
public void withdraw(double amount) {
System.out.println(this.owner + "取款" + amount);
state.withdraw(amount); //调用状态对象的withdraw()方法
System.out.println("现在余额为" + this.balance);
System.out.println("现在账号状态为" + this.state.getClass().getName());
System.out.println("-----------------------------");
}
//查看状态
public void computeInterest() {
state.computeInterest(); // 调用状态对象的computeInterest()方法
}
}
抽象状态类
public abstract class AccountState {
protected Account acc;
//存款
public abstract void deposit(double amount);
//取款
public abstract void withdraw(double amount);
//显示所属状态
public abstract void computeInterest();
//状态装换
public abstract void stateCheck();
}
正常状态:具体状态类
public class NormalState extends AccountState {
public NormalState(Account acc) {
this.acc = acc;
}
public NormalState(AccountState state) {
this.acc = state.acc;
}
@Override
public void deposit(double amount) {
acc.setBalance(acc.getBalance() + amount);
stateCheck();
}
@Override
public void withdraw(double amount) {
acc.setBalance(acc.getBalance() - amount);
stateCheck();
}
@Override
public void computeInterest() {
System.out.println("正常状态,无须支付利息!");
}
//状态转换
public void stateCheck() {
if(acc.getBalance() > -2000 && acc.getBalance() <= 0) {
acc.setState(new OverdraftState(this));
}
else if(acc.getBalance() == -2000) {
acc.setState(new RestrictedState(this));
}else if(acc.getBalance() < -2000) {
System.out.println("操作受限");
}
}
}
透支状态:具体状态类
public class OverdraftState extends AccountState {
public OverdraftState(AccountState acc) {
this.acc = acc.acc;
}
@Override
public void deposit(double amount) {
acc.setBalance(acc.getBalance() + amount);
stateCheck();
}
@Override
public void withdraw(double amount) {
acc.setBalance(acc.getBalance() - amount);
stateCheck();
}
@Override
public void computeInterest() {
System.out.println("计算利息");
}
//状态转换
public void stateCheck() {
if(acc.getBalance() > 0) {
acc.setState(new NormalState(this));
}
else if(acc.getBalance() == -2000) {
acc.setState(new RestrictedState(this));
}
else if(acc.getBalance() < -2000) {
System.out.println("操作受限");
}
}
}
受限状态:具体状态类
public class RestrictedState extends AccountState {
public RestrictedState(AccountState state) {
this.acc = state.acc;
}
@Override
public void deposit(double amount) {
acc.setBalance(acc.getBalance() + amount);
stateCheck();
}
@Override
public void withdraw(double amount) {
System.out.println("账号受限,取款失败");
}
@Override
public void computeInterest() {
System.out.println("计算利息!");
}
//状态转换
public void stateCheck() {
if(acc.getBalance() > 0) {
acc.setState(new NormalState(this));
}
else if(acc.getBalance() > -2000) {
acc.setState(new OverdraftState(this));
}
}
}
客户端
class Client{
public static void main(String args[]){
Account acc = new Account("段誉", 0.0);
acc.deposit(1000);
acc.withdraw(2000);
acc.deposit(3000);
acc.withdraw(4000);
acc.withdraw(1000);
acc.computeInterest();
}
}
共享状态
在有些情况下,多个环境对象可能需要共享同一个状态,如果希望在系统中实现多个环境对象共享一个或多个状态对象,那么需要将这些状态对象定义为环境类的静态成员对象。
实例
某系统要求两个开关要么都处于开的状态,要么都处于关的状态,在使用时他们的状态必须保持一致,开关可以由开转换到关,也可以由关转换到开
结构图
代码实现
开关类Switch
public class Switch {
private static State state, onState, offState; //定义3个静态的状态对象
private String name;
public Switch(String name) {
this.name = name;
onState = new OnState();
offState = new OffState();
this.state = onState;
}
public void setState(State state) {
this.state = state;
}
public static State getState(String type) {
if(type.equalsIgnoreCase("on")) {
return onState;
}else {
return offState;
}
}
//打开开关
public void on(){
System.out.println(name);
state.on(this);
}
//关闭开关
public void off() {
System.out.println(name);
state.off(this);
}
}
抽象状态类State
public abstract class State {
public abstract void on(Switch s);
public abstract void off(Switch s);
}
两个具体状态类OnState和OffState:
打开状态
public class OnState extends State {
@Override
public void on(Switch s) {
System.out.println("已经打开了!");
}
@Override
public void off(Switch s) {
System.out.println("已经关闭了!");
s.setState(Switch.getState("off"));
}
}
关闭状态
public class OffState extends State {
@Override
public void on(Switch s) {
System.out.println("打开");
s.setState(Switch.getState("on"));
}
@Override
public void off(Switch s) {
System.out.println("已经关闭!");
}
}
使用环境类实现状态转换
在状态模式中实现状态转换时,具体状态类可通过环境类Context的setState()方法进行状态的转换操作,也可以统一由环境类Contextn来实现状态的转换。此时增加新的具体状态类可能需要修改其他具体状态类或者环境类的源代码,否则系统无法转换到新增状态,但对于客户端来说,无需关心状态类,可以为环境类设置默认的状态类,而将状态的转换工作交给具体状态类或环境类来完成,具体的转换细节对于客户端来说是透明的
在上面的银行账号状态设计实例中,通过具体状态类来实现状态的转换,在每个具体状态类中都包含一个stateCheck()方法,在该方法内部实现状态的转换。此时还可以通过环境类来实现状态转换,环境类作为一个状态管理器,统一实现各种状态之间的转换操作。
实例:放大镜的设计
开发一个屏幕放大器,其具体功能描述为:用户单击‘放大镜’按钮之后屏幕将放大一倍,再点击一次‘放大镜’按钮屏幕再放大一倍,第3次单击该按钮后屏幕将还原到默认大小
结构图
使用状态模式来设计屏幕放大器,定义3个屏幕状态类NormalState,largerState和LargestState来对应屏幕的3中状态,分别是正常状态,2倍方法状态和4倍放大状态,屏幕类Screen充当环境类
代码实现
屏幕类
public class Screen {
//枚举所有的状态,currentState表示当前状态
private State currentState, normalState, largerState, largestState;
public Screen() {
this.normalState = new NormalState(); //创建正常状态对象
this.largerState = new LargerState(); //创建2倍放大状态对象
this.largestState = new LargestState(); //创建4倍放大状态对象
this.currentState = normalState; //设置初始状态
this.currentState.dispaly();
}
public void setState(State state) {
this.currentState = state;
}
//单击事件处理方法,封装了对状态类中业务方法的调用和状态的转换
public void onClick() {
if(this.currentState == normalState) {
this.setState(largerState);
this.currentState.dispaly();
}
else if(this.currentState == largerState) {
this.setState(largestState);
this.currentState.dispaly();
}
else if(this.currentState == largestState) {
this.setState(normalState);
this.currentState.dispaly();
}
}
}
抽象状态类
public abstract class State {
public abstract void dispaly();
}
正常状态类
public class NormalState extends State {
@Override
public void dispaly() {
System.out.println("正常大小!");
}
}
2倍状态类
public class LargerState extends State{
@Override
public void dispaly() {
System.out.println("2倍大小");
}
}
4倍状态类
public class LargestState extends State {
@Override
public void dispaly() {
System.out.println("4倍大小");
}
}
在上述代码中,所有的状态转换都由环境类Screen来实现,此时环境类充当了状态管理器角色,如果需要增加新的状态,例如,8倍状态类,需要修改环境类,这在一定程度上违背了开闭原则,但对其他状态类没有任何影响。
总结
状态模式将一个对象在不同状态下的不同行为封装到一个个状态类中,通过设置不同的状态对象可以让环境对象拥有不同的行为,而状态转换的细节对于客户端而言是透明的,方便了客户端的使用,在实际开发中,状态模式具有较高的使用频率,在工作流,游戏等软件模式都得到广泛应用,例如公文状态的装换,游戏角色的升级等。
主要优点
封装了状态的装换规则,在状态模式中可以将状态的装换代码封装在环境类或者具体状态类中,可以对装换代码进行集中管理,而不是分散在一个个业务方法中
将所有与某个状态有关的行为放在一个类中,只需注入一个不同的状态对象即可使环境对象拥有不同的行为
允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起
可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数
主要缺点
状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大
状态模式的程序结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度
状态模式对开闭原则的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需要修改对应类的源代码。
适用场景
对象的行为依赖于它的状态(例如某些属性值),状态的改变将导致行为的变化
在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强