场景
在软件系统中,有些对象也像水一样具有多种状态,这些状态在某些情况下能够相互转换,而且对象在不同的状态下也将具有不同的行为。
为了更好地对这些具有多种状态的对象进行设计,我们可以使用一种被称之为状态模式的设计模式。
状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。当系统中某个对象存在多个状态,
这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式。
状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化,
对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致处理。
状态模式(State Pattern):
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),
状态模式是一种对象行为型模式。在状态模式中引入了抽象状态类和具体状态类,它们是状态模式的核心,其结构如图所示
在状态模式结构图中包含如下几个角色:
● Context(环境类):
环境类又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,
因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,
在具体实现时,它是一个State子类的对象。
● State(抽象状态类):
它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,
而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,
相同的方法可以写在抽象状态类中。
● ConcreteState(具体状态类):它是抽象状态类的子类,每一个子类实现一个与环境类的一个
状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。
注:
博客:
霸道流氓气质-****博客
实现
1、示例场景
软件公司欲为某银行开发一套信用卡业务系统,银行账户(Account)是该系统的核心类之一,
通过分析,Sunny软件公司开发人员发现在该系统中,账户存在三种状态,且在不同状态下账户存在不同的行为,
具体说明如下:
(1) 如果账户中余额大于等于0,则账户的状态为正常状态(Normal State),此时用户既可以向该账户存款也可以从该账户取款;
(2) 如果账户中余额小于0,并且大于-2000,则账户的状态为透支状态(Overdraft State),此时用户既可以向该账户存款也可以从该账户取款,
但需要按天计算利息;
(3) 如果账户中余额等于-2000,那么账户的状态为受限状态(Restricted State),此时用户只能向该账户存款,不能再从中取款,
同时也将按天计算利息;
(4) 根据余额的不同,以上三种状态可发生相互转换。
2、新建银行账户类:环境类
/**
* 银行账户:环境类
*/
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().getSimpleName());
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().getSimpleName());
System.out.println("---------------------------------");
}
public void computeInterest(){
state.computeInterest(); //调用状态对象的computeInterest方法
}
}
3、新建抽象状态类
/**
* 抽象状态类
*/
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();
}
4、新建正常状态类:具体状态类
/**
* 正常状态类:具体状态类
*/
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("正常状态,无须支付利息!");
}
//状态转换
@Override
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("操作受限!");
}
}
}
5、新建透支状态类:具体状态类
//透支状态类:具体状态类
public class OverdraftState extends AccountState {
public OverdraftState(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("计算利息");
}
@Override
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("操作受限!");
}
}
}
6、新建受限状态类:具体状态类
//受限状态:具体状态类
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("计算利息!");
}
@Override
public void stateCheck() {
if(acc.getBalance()>0){
acc.setState(new NormalState(this));
}else if(acc.getBalance()>-2000){
acc.setState(new OverdraftState(this));
}
}
}
7、编写客户端测试
public class StatePatternClient {
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
现在账户状态为NormalState
---------------------------------
张三取款2000.0
现在余额为-1000.0
现在账户状态为OverdraftState
---------------------------------
张三存款3000.0
现在余额为2000.0
现在账户状态为NormalState
---------------------------------
张三取款4000.0
现在余额为-2000.0
现在账户状态为RestrictedState
---------------------------------
张三取款1000.0
账号受限,取款失败
现在余额为-2000.0
现在账户状态为RestrictedState
---------------------------------
计算利息!
Process finished with exit code 0
8、总结
状态模式将一个对象在不同状态下的不同行为封装在一个个状态类中,通过设置不同的状态对象可以让环境对象拥有不同的行为,
而状态转换的细节对于客户端而言是透明的,方便了客户端的使用。在实际开发中,状态模式具有较高的使用频率,
在工作流和游戏开发中状态模式都得到了广泛的应用,例如公文状态的转换、游戏中角色的升级等。
1. 主要优点
状态模式的主要优点如下:
(1) 封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,可以对状态转换代码进行集中管理,
而不是分散在一个个业务方法中。
(2) 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。
(3) 允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以让我们避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起。
(4) 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
1. 主要缺点
状态模式的主要缺点如下:
(1) 状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大。
(2) 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度。
(3) 状态模式对“开闭原则”的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,
否则无法转换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。
1. 适用场景
在以下情况下可以考虑使用状态模式
(1) 对象的行为依赖于它的状态(如某些属性值),状态的改变将导致行为的变化。
(2) 在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,
并且导致客户类与类库之间的耦合增强。