一、场景问题
某银行需要设计一套信用卡账户管理系统,其中账户大约分为三种状态
-
正常状态
账户余额大于等于0,即不存在欠款情况。此时用户可以存款,也可以取款。
-
透支状态
账户余额小于0,且欠款金额小于5000。用户可以存款,也可以取款。
-
受限状态
账户的欠款金额大于等于5000,用户无法继续取款,仅仅能够存款。
请设计一套可用的信用卡账户存钱取款程序。
二、传统解决方案
1、解决思路
每次存款取款校验状态判断是否可操作
-
代码展示
-
账户类
public class Account { /** * 开户名 */ private String owner; /** * 账户余额 */ private double amount = 0; public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } public double getAmount() { return amount; } public void setAmount(double amount) { this.amount = amount; } public Account(String owner) { this.owner = owner; } /** * 存款 * @param money 1 * @return void **/ public void deposit(double money) { System.out.println(owner + "存款" + money); amount += money; System.out.println("当前余额为" + amount + "现在帐户状态为" + getAccountState()); System.out.println("--------------------------------------------------------"); } /** * 取款 * @param money 1 * @return void **/ public void withdraw(double money) { System.out.println(owner + "取款" + money); String accountState = getAccountState(); if ("受限".equals(accountState)) { System.out.println("账户处于受限状态,无法取款!"); System.out.println("--------------------------------------------------------"); return; } amount -= money; System.out.println("当前余额为" + amount + "现在帐户状态为" + getAccountState()); System.out.println("--------------------------------------------------------"); } /** * 获取账户状态 * @return java.lang.String **/ public String getAccountState() { if (amount > 0) { return "正常"; } else if (amount < 0 && amount > -2000) { return "透支"; } else { return "受限"; } } }
-
-
客户端测试
public static void main(String[] args) { Account account = new Account("张三"); //存款5000 account.deposit(5000); //分别取款6000、5000、5000 account.withdraw(6000); account.withdraw(5000); account.withdraw(5000); //存款10000 account.deposit(10000); }
输出
张三存款5000.0 当前余额为5000.0现在帐户状态为正常 -------------------------------------------------------- 张三取款6000.0 当前余额为-1000.0现在帐户状态为透支 -------------------------------------------------------- 张三取款5000.0 当前余额为-6000.0现在帐户状态为受限 -------------------------------------------------------- 张三取款5000.0 账户处于受限状态,无法取款! -------------------------------------------------------- 张三存款10000.0 当前余额为4000.0现在帐户状态为正常 --------------------------------------------------------
2、弊端
- 每个操作都需要一系列的状态判断,代码冗余,维护性差
- 如果新增一种新的状态就需要在源代码上进行大规模改动,违背开闭原则。
三、模式剖析
1、模式定义
状态模式(State Pattern):对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
2、模式结构
状态模式一般包含如下角色
-
Context
:环境角色也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
//环境类 class Context { //持有一个状态的引用 private State state; public Context() { this.state=new ConcreteStateA(); } public void setState(State state) { this.state=state; } public State getState() { return(state); } //对请求做处理 public void Handle() { state.Handle(this); } }
-
State
:抽象状态角色抽象类或者接口,以封装环境对象中的特定状态所对应的行为。
//抽象状态类 abstract class State { public abstract void Handle(Context context); }
-
Concrete State
:具体状态角色实现抽象状态所对应的行为
//具体状态A类 class ConcreteStateA extends State { public void Handle(Context context) { System.out.println("当前状态是 A."); //状态转移 context.setState(new ConcreteStateB()); } } //具体状态B类 class ConcreteStateB extends State { public void Handle(Context context) { System.out.println("当前状态是 B."); //状态转移 context.setState(new ConcreteStateA()); } }
3、模式结构图
4、使用状态模式改进案例
4.1、代码展示
-
账户类(剔除状态判断)
public class Account { /** * 维持一个对抽象状态对象的引用 */ private AccountState state; /** * 开户名 */ private String owner; /** * 账户余额 */ private double amount = 0; public AccountState getState() { return state; } public void setState(AccountState state) { this.state = state; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } public double getAmount() { return amount; } public void setAmount(double amount) { this.amount = amount; } public Account(String owner) { this.owner = owner; this.state = new NormalAccountState(this); } /** * 存款 * @param money 1 * @return void **/ public void deposit(double money) { System.out.println(owner + "存款" + money); state.deposit(money); System.out.println("当前余额为" + amount + "现在帐户状态为" + state.desc); System.out.println("--------------------------------------------------------"); } /** * 取款 * @param money 1 * @return void **/ public void withdraw(double money) { System.out.println(owner + "取款" + money); state.withdraw(money); System.out.println("当前余额为" + amount + "现在帐户状态为" + state.desc); System.out.println("--------------------------------------------------------"); } }
这里包含了状态的引用,由不同的状态来控制不同的行为
-
抽象状态类
public abstract class AccountState { /** * 账户状态描述 */ protected String desc; /** * 账户 */ protected Account account; /** * 存款 * @param money 1 * @return void **/ public abstract void deposit(double money); /** * 取款 * @param money 1 * @return void **/ public abstract void withdraw(double money); /** * 状态转换 * @return void **/ public abstract void changeState(); }
-
具体状态类
-
正常状态
public class NormalAccountState extends AccountState { public NormalAccountState(Account account) { desc = "正常"; super.account = account; } @Override public void deposit(double money) { account.setAmount(account.getAmount() + money); changeState(); } @Override public void withdraw(double money) { account.setAmount(account.getAmount() - money); changeState(); } @Override public void changeState() { double amount = account.getAmount(); //账户余额为(-5000,0)之间,需要转移状态为透支状态 if (amount < 0 && amount > -5000) { account.setState(new OverdraftAccountState(account)); } //账户余额为(负无穷,-5000]之间,需要转移状态为受限状态 else if (amount <= -5000) { account.setState(new RestrictedAccountState(account)); } } }
-
透支状态
public class OverdraftAccountState extends AccountState { public OverdraftAccountState(Account account) { super.desc = "透支"; super.account = account; } @Override public void deposit(double money) { account.setAmount(account.getAmount() + money); changeState(); } @Override public void withdraw(double money) { account.setAmount(account.getAmount() - money); changeState(); } @Override public void changeState() { double amount = account.getAmount(); //账户余额为[0,正无穷)之间,需要转移状态为正常状态 if (amount > 0) { account.setState(new NormalAccountState(account)); } //账户余额为(负无穷,-5000]之间,需要转移状态为受限状态 else if (amount <= -5000) { account.setState(new RestrictedAccountState(account)); } } }
-
受限状态
public class RestrictedAccountState extends AccountState { public RestrictedAccountState(Account account) { super.desc = "受限"; super.account = account; } @Override public void deposit(double money) { account.setAmount(account.getAmount() + money); changeState(); } @Override public void withdraw(double money) { System.out.println("账户处于受限状态,无法取款!"); } @Override public void changeState() { double amount = account.getAmount(); //账户余额为[0,正无穷)之间,需要转移状态为正常状态 if (amount >= 0) { account.setState(new NormalAccountState(account)); } //账户余额为(-5000,0)之间,需要转移状态为透支状态 else if (amount < 0 && amount > -5000) { account.setState(new OverdraftAccountState(account)); } } }
-
4.2、客户端测试
public static void main(String[] args) {
//创建账户
Account account = new Account("张三");
//存款5000
account.deposit(5000);
//分别取款6000、5000、5000
account.withdraw(6000);
account.withdraw(5000);
account.withdraw(5000);
//存款10000
account.deposit(10000);
}
输出
张三存款5000.0
当前余额为5000.0现在帐户状态为正常
--------------------------------------------------------
张三取款6000.0
当前余额为-1000.0现在帐户状态为透支
--------------------------------------------------------
张三取款5000.0
当前余额为-6000.0现在帐户状态为受限
--------------------------------------------------------
张三取款5000.0
账户处于受限状态,无法取款!
当前余额为-6000.0现在帐户状态为受限
--------------------------------------------------------
张三存款10000.0
当前余额为4000.0现在帐户状态为正常
--------------------------------------------------------
5、优缺点
- 优点
- 封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
- 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以让我们避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数
- 缺点
- 状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度。
- 状态模式对“开闭原则”的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。
6、使用场景
- 对象的行为依赖于它的状态(如某些属性值),状态的改变将导致行为的变化。
- 在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强