【设计模式】行为型模式其八: 状态模式

235 阅读9分钟

状态模式

水的三种状态 image.png

分析:

在软件系统中:

  • 有些对象具有多种状态
  • 这些状态在某些情况下能够相互转换
  • 对象在不同的状态下将具有不同的行为
  • 复杂的条件判断语句来进行状态的判断和转换操作 -> 导致代码的可维护性和灵活性下降 ->出现新的状态时,代码的扩展性很差,客户端代码也需要进行相应的修改,违背了开闭原则

状态模式的定义

  • 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
  • 又名状态对象(Objects for States)
  • 用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题
  • 将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化
  • 对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致处理

与策略模式的比较

大家可能觉得策略模式与状态模式有点类似。

  • 策略模式根据传入的参数选择不同策略。
  • 状态模式根据对象当前状态选择不同方法。

详细来说:

▶ 策略模式:

  • Client向Context传入一个Strategy类型的参数。
  • Context根据传入的参数,为Client选择和注入相应的Strategy。
  • Client 使用注入Strategy对象 的环境类的 方法。

▶ 状态模式:

  • Context根据自身当前状态来选择一个State类。
  • State类实现具体的行为方法。
  • Context把行为方法委托给对应的State对象。

状态模式的结构

image.png

状态模式包含以下3个角色:

  • Context(环境类)
  • State(抽象状态类)
  • ConcreteState(具体状态类)

状态模式的实现

(1) 统一由环境类来负责状态之间的转换,环境类充当了状态管理器(State Manager)角色:

判断环境类的属性值,根据属性值进行状态转换

(2) 由具体状态类来负责状态之间的转换,可以在具体状态类的业务方法中判断环境类的某些属性值,再根据情况为环境类设置新的状态对象,实现状态转换

根据环境对象中的属性值进行状态转换,不过这里是在具体状态类判断

实例学习

某软件公司要为一银行开发一套信用卡业务系统,银行账户(Account)是该系统的核心类之一。通过分析,该软件公司开发人员发现在系统中账户存在3种状态,且在不同状态下账户存在不同的行为,具体说明如下:

(1) 如果账户中余额大于等于0,则账户的状态为正常状态(Normal State),此时用户既可以向该账户存款也可以从该账户取款;

(2) 如果账户中余额小于0,并且大于-2000,则账户的状态为透支状态(Overdraft State),此时用户既可以向该账户存款也可以从该账户取款,但需要按天计算利息;

(3) 如果账户中余额等于-2000,那么账户的状态为受限状态(Restricted State),此时用户只能向该账户存款,不能再从中取款,同时也将按天计算利息;

(4) 根据余额的不同,以上3种状态可发生相互转换。

现使用状态模式设计并实现银行账户状态的转换

状态类之间的转化

image.png

(1) Account:银行账户,充当环境类
(2) AccountState:账户状态类,充当抽象状态类
(3) NormalState:正常状态类,充当具体状态类
(4) OverdraftState:透支状态类,充当具体状态类
(5) RestrictedState:受限状态类,充当具体状态类
(6) Client:客户端测试类

抽象状态类

// 定义了四个方法, 前两个分别为存款与消费。
// 第三个方法用于判断当前状态是否需要计算利息
// 第四个方法用于状态的转化

//抽象状态类
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;
   }
      
   public void deposit(double amount) {
      acc.setBalance(acc.getBalance() + amount);
      stateCheck();
   }
   
   public void withdraw(double amount) {
      acc.setBalance(acc.getBalance() - amount);
      stateCheck();
   }
   
   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 state) {
      this.acc = state.acc;
   }
   
   public void deposit(double amount) {
      acc.setBalance(acc.getBalance() + amount);
      stateCheck();
   }
   
   public void withdraw(double amount) {
      acc.setBalance(acc.getBalance() - amount);
      stateCheck();
   }
   
   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;
   }
   
   public void deposit(double amount) {
      acc.setBalance(acc.getBalance() + amount);
      stateCheck();
   }
   
   public void withdraw(double amount) {
      System.out.println("帐号受限,取款失败");
   }
   
   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));
      }
   }
}

环境类

// 维持一个抽象状态的引用,存储当前的具体状态 // 拥有自己的方法和属性
// 调用方法实际是调用具体状态类的方法,延迟执行

//银行账户:环境类
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 this.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 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();
   }
}

输出及分析

段誉开户,初始金额为0.0

段誉存款1000.0
现在余额为1000.0
现在帐户状态为designpatterns.state.NormalState
-------------------------------------------

段誉取款2000.0
现在余额为-1000.0
现在账户状态为designpatterns.state.OverdraftState
----------------------------------------------

段誉存款3000.0
现在余额为2000.0
现在帐户状态为designpatterns.state.NormalState

-------------------------------------------

段誉取款4000.0
现在余额为-2000.0
现在帐户状态为designpatterns.state.RestrictedState
-----------------------------------------------

段誉取款1000.0
帐号受限,取款失败
现在余额为-2000.0
现在帐户状态为designpatterns.state.RestrictedState
-----------------------------------------------

计算利息!

分析:

  1. 创建环境类Account, 输入姓名和初始值, 默认是正常状态
  2. 存款1000元,调用具体状态类的存款方法,进入之后进行状态判断;具体状态类去改变环境类的余额属性。
  3. 然后取款2000元,调用具体状态类的取款操作,然后进入具体状态类进行状态判断,判断当前余额-1000,所以转化环境类抽象状态指向超支状态。
  4. 然后存款3000,恢复为正常状态
  5. 取款4000, 转化为受限状态,因此无法取款,显示取款失败

模式优缺点

模式优点

  • 封装了状态的转换规则,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中
  • 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为
  • 允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,可以避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起
  • 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数

模式缺点

  • 会增加系统中类和对象的个数,导致系统运行开销增大
  • 结构与实现都较为复杂,如果使用不当将导致程序结构和代码混乱,增加系统设计的难度
  • 开闭原则的支持并不太好,增加新的状态类需要修改负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需要修改对应类的源代码

模式适用环境

  • 对象的行为依赖于它的状态(例如某些属性值),状态的改变将导致行为的变化
  • 在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强

模式简化if-else

状态模式本身不完全避免if-else,但意义在于降低复杂度。

具体来说:

  1. 状态模式确实是一种从大量的if-else中提取出状态逻辑的方式。

  2. 但是具体状态类里面还是需要根据环境类的属性来判断是否需要转移状态。这仍然会使用if-else

  3. 但是通过状态模式实现,if-else逻辑分散在不同状态类中,降低了环境类的复杂度。

  4. 状态模式最大的价值还在于:

    • 提取出状态作为类,使得状态对象有自己的行为和状态转移逻辑。
    • 提高拓展性,可以方便地添加新的状态类。
    • 状态对象之间解耦,有利于代码维护。

所以总的来说,状态模式虽然不完全避免if-else,但代码会更加结构清晰、可扩展和可维护。避免了大量复杂的嵌套if-else语句。