这个状态模式之前老四没接触过,甚至不知道这个名字,参考了一些资料尝试着写一写。就像策略模式,状态模式其实也属于行为模式,关于策略模式您可以参考老四写的这篇《浅析设计模式第二章之策略模式》。
我们都知道在我们经常碰到的业务场景中,经常会碰到一个对象会有多种状态,每种状态可能还要对应不同的对象行为,你的数据库设计经常也会要设计某个字段为状态字段,用来判断或者临界某种业务场景。比如说游戏角色的升级涉及到各种装备的进账等等,再比如说订单有待支付、待处理、已发货、收货完成等。其中待支付状态不能进行发货处理,待处理用户不能确认收货等等,每种状态是对应对象的不同的行为的。这些都涉及到状态模式,就当前这个例子来讲,如果我们设计一个超级订单类,那么这里面会存在很多的中订单状态判断的if-else if-else语句,我们都知道大量的条件判断有很多缺点:
- 判断条件下的方法实现不尽相同导致代码冗长,可维护性极差
- 代码测试难度较大,针对每种对象状态的测试都不利于维护
- 扩展性几乎没有,新增一种状态或者修改某种对象状态,不仅要修改条件代码,还要改变对应的对象行为方法,非常麻烦。
我们都知道方法过长是坏味道,面向对象设计其实就是希望做到代码的责任分解,符合单一职责原则,关于单一职责原则您可以参考老四的这篇文章《浅析设计模式第三章之单一职责原则》,那么这个时候可能就需要状态模式来帮忙解决这个问题,让我们一点点来了解状态模式是如何解决对象多状态多行为的问题的,他还能扩展性的解决什么问题?
定义
状态模式(State): 当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。其别名为状态对象(Objects for States)。
听/看起来还是有点懵逼,其实状态模式解决的就是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一些列具体的类当中,把判断逻辑简单化处理。
还是敲代码清晰一些,这里我们使用银行账户的例子: 一个用户的银行卡账户分为正常、透支、受限三种状态,正常状态可以正常存取人民币,透支状态在一定额度之内可以正常存取人民币但是要支付利息,受限状态就是只能存钱不能取钱了,并且还要付利息。所以在这个例子中,用户银行卡里面的余额决定着该用户的账户状态,三种状态是随时可能变换的,对应用户的功能也会有对应的限制,我们首先设计一个超级银行账户类,看看不使用状态模式下的类设计是什么样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
package com.glorze.common; /** * 普通用户账户类 * * @ClassName BankAccount * @author: glorze.com * @since: 2018年6月20日 下午1:18:32 */ public class BankAccount { /** * 正常状态 */ public static final String NORMAL_STATE = "NormalState"; /** * 透支状态 */ public static final String OVERDRAFT_STATE = "OverdraftState"; /** * 受限状态 */ public static final String RESTRICTED_STATE = "RestrictedState"; public static final Integer OVER_POINT = -2000; /** * 当前银行账户状态 */ private String state; /** * 当前用户账户余额 */ private Integer balance; /** * 存款操作 * @Title: deposit * @return void * @author: 高老四博客 * @since: 2018年6月20日 下午1:24:51 */ public void deposit() { System.out.println("正常的存钱操作"); stateCheck(); } /** * 取款操作 * @Title: withdraw * @return void * @author: 高老四博客 * @since: 2018年6月20日 下午1:25:03 */ public void withdraw() { if (state.equalsIgnoreCase(NORMAL_STATE) || state.equalsIgnoreCase(OVERDRAFT_STATE)) { System.out.println("正常的取款操作"); stateCheck(); } else { System.out.println("当前账户操作受限,不允许取款!"); } } /** * 状态检查和状态转换 * @Title: stateCheck * @return void * @author: 高老四博客 * @since: 2018年6月20日 下午1:24:16 */ public void stateCheck() { if (balance >= 0) { state = "NormalState"; } else if (balance > OVER_POINT && balance < 0) { System.out.println("计算利息"); state = "OverdraftState"; } else { System.out.println("计算利息"); state = "RestrictedState"; } } } |
以上代码基本都实现了老四前面写的条件判断的三条缺点,所以我们接下来根据状态模式的定义,来改写上述代码。根据三种不同的账户状态设计三个具体的状态类,分别为账户正常状态类、透支状态类以及受限状态类,他们都实现了银行账户的抽象状态接口供环境类调用。所以,其实再设计角度上来讲状态模式与策略模式差不多,都包含如下几个角色:
- Context(环境类): 环境类又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象。
- State(抽象状态类): 它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
- ConcreteState(具体状态类): 它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。
但有所不同的是,状态模式中对象的状态是互相转换的,所以负责的状态转换的处理你既可以放到环境类中来处理,也可以放入到具体的状态类来处理,但是我推荐放到环境类当中,至于原因,接下来我们用状态模式实现上述代码的改造就清晰了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
package com.glorze.state; import java.math.BigDecimal; /** * 银行账户环境类 * @ClassName BankAccount * @author: glorze.com * @since: 2018年6月20日 下午2:44:49 */ public class BankAccount { /** * 维持一个对抽象状态对象的引用 */ private BaseAccountState accountState; /** * 户名 */ private String owner; /** * 账户余额,默认为0,正常状态 */ private BigDecimal balance = new BigDecimal("0"); public BankAccount(String owner, BigDecimal balance) { super(); // 默认就是正常状态 this.accountState = new NormalState(this); this.owner = owner; this.balance = balance; } public BaseAccountState getAccountState() { return accountState; } public void setAccountState(BaseAccountState accountState) { this.accountState = accountState; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } public BigDecimal getBalance() { return balance; } public void setBalance(BigDecimal balance) { this.balance = balance; } /** * 存钱 * @Title: deposit * @param amount * @return void * @author: 高老四博客 * @since: 2018年6月20日 下午11:20:56 */ public void deposit(BigDecimal amount) { System.out.println(this.owner + "存款" + amount); accountState.deposit(amount); System.out.println("现在余额为" + this.balance); System.out.println("现在帐户状态为" + this.accountState.getClass().getName()); System.out.println("---------------------------------------------"); } /** * 取钱 * @Title: withdraw * @param amount * @return void * @author: 高老四博客 * @since: 2018年6月20日 下午11:22:35 */ public void withdraw(BigDecimal amount) { System.out.println(this.owner + "取款" + amount); accountState.withdraw(amount); System.out.println("现在余额为" + this.balance); System.out.println("现在帐户状态为" + this.accountState.getClass().getName()); System.out.println("---------------------------------------------"); } /** * 计算利息 * @Title: computeInterest * @return void * @author: 高老四博客 * @since: 2018年6月20日 下午11:22:53 */ public void computeInterest() { accountState.computeInterest(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
package com.glorze.state; import java.math.BigDecimal; /** * 抽象状态类 * @ClassName AccountState * @author: glorze.com * @since: 2018年6月20日 下午2:13:09 */ public abstract class BaseAccountState { /** * 引入环境类对象 */ protected BankAccount bankAccount; /** * 存钱 * @Title: deposit * @param amount * @return void * @author: 高老四博客 * @since: 2018年6月20日 下午2:14:23 */ public abstract void deposit(BigDecimal amount); /** * 取钱 * @Title: withdraw * @param amount * @return void * @author: 高老四博客 * @since: 2018年6月20日 下午2:14:35 */ public abstract void withdraw(BigDecimal amount); /** * 计算利息 * @Title: computeInterest * @return void * @author: 高老四博客 * @since: 2018年6月20日 下午2:14:43 */ public abstract void computeInterest(); /** * 账户状态检查 * @Title: stateCheck * @return void * @author: 高老四博客 * @since: 2018年6月20日 下午2:14:53 */ public abstract void stateCheck(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
package com.glorze.state; import java.math.BigDecimal; /** * 正常状态具体类 * @ClassName NormalState * @author: glorze.com * @since: 2018年6月20日 下午11:35:58 */ public class NormalState extends BaseAccountState { public NormalState(BankAccount bankAccount) { this.bankAccount = bankAccount; } public NormalState(BaseAccountState accountState) { this.bankAccount = accountState.bankAccount; } @Override public void deposit(BigDecimal amount) { bankAccount.setBalance(bankAccount.getBalance().add(amount)); stateCheck(); } @Override public void withdraw(BigDecimal amount) { bankAccount.setBalance(bankAccount.getBalance().subtract(amount)); stateCheck(); } @Override public void computeInterest() { System.out.println("计算利息-one!"); } @Override public void stateCheck() { System.out.println("normal check"); BigDecimal overdraftState = new BigDecimal("-2000"); BigDecimal normalState = new BigDecimal("0"); if(bankAccount.getBalance().compareTo(normalState) >= 0){ bankAccount.setAccountState(new NormalState(bankAccount)); } else if (bankAccount.getBalance().compareTo(overdraftState) ==1 && bankAccount.getBalance().compareTo(normalState) <= 0) { bankAccount.setAccountState(new OverdraftState(bankAccount)); } else { bankAccount.setAccountState(new RestrictedState(bankAccount)); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.glorze.state; import java.math.BigDecimal; public class TestState { public static void main(String[] args) { BankAccount bankAccount = new BankAccount("高老四", new BigDecimal(0)); bankAccount.deposit(new BigDecimal("888")); bankAccount.withdraw(new BigDecimal("1000")); bankAccount.deposit(new BigDecimal("666")); bankAccount.withdraw(new BigDecimal("1888")); bankAccount.withdraw(new BigDecimal("1889")); bankAccount.withdraw(new BigDecimal("1890")); bankAccount.computeInterest(); } } |
以上老四具体的状态实现类只列举了正常状态实现类,项目所有源码请文末自助获取下载。
如你所见,我们在环境类当中免除了一系列复杂的状态条件判断,银行账户类只专注于存取款的操作,每种状态相当于后台监控,遇到操作权限不足的时候会自动提示给用户。所以对于这种多种状态互相转换的情况,状态模式再适合不过了,接下来我们继续举一反三,状态模式可以扩展性支持多种情况,比如下面这种情形:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
package com.glorze.onoff; /** * 开关状态抽象类 * @ClassName BaseSwitchState * @author: glorze.com * @since: 2018年6月23日 下午9:41:54 */ public abstract class BaseSwitchState { /** * 开关状态-开 * @Title: on * @param s * @return void * @author: 高老四博客 * @since: 2018年6月23日 下午9:42:27 */ public abstract void on(Switch s); /** * 开关状态-关 * @Title: off * @param s * @return void * @author: 高老四博客 * @since: 2018年6月23日 下午9:42:42 */ public abstract void off(Switch s); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package com.glorze.onoff; /** * 状态为开的状态类 * @ClassName OnState * @author: glorze.com * @since: 2018年6月23日 下午10:32:26 */ public class OnState extends BaseSwitchState { @Override public void on(Switch s) { System.out.println("开关打开"); } @Override public void off(Switch s) { System.out.println("开关关闭"); s.setState(Switch.getState("off")); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package com.glorze.onoff; /** * 状态为关的状态类 * @ClassName OffState * @author: glorze.com * @since: 2018年6月23日 下午10:32:38 */ public class OffState extends BaseSwitchState { @Override public void on(Switch s) { System.out.println("开关打开"); s.setState(Switch.getState("on")); } @Override public void off(Switch s) { System.out.println("开关关闭"); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
package com.glorze.onoff; /** * 开关控制类 * @ClassName Switch * @author: glorze.com * @since: 2018年6月23日 下午10:32:14 */ public class Switch { private static BaseSwitchState state; private static BaseSwitchState onState; private static BaseSwitchState offState; private String name; public Switch(String name) { this.name = name; onState = new OnState(); offState = new OffState(); Switch.state = onState; } public void setState(BaseSwitchState state) { Switch.state = state; } public static BaseSwitchState getState(String type) { if (type.equalsIgnoreCase("on")) { return onState; } else { return offState; } } /** * 打开开关 * @Title: on * @return void * @author: 高老四博客 * @since: 2018年6月23日 下午9:46:42 */ public void on() { System.out.print(name); state.on(this); } /** * 关闭开关 * @Title: off * @return void * @author: 高老四博客 * @since: 2018年6月23日 下午9:46:34 */ public void off() { System.out.print(name); state.off(this); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package com.glorze.onoff; public class TestSwitch { public static void main(String args[]) { Switch s1, s2; s1 = new Switch("开关1"); s2 = new Switch("开关2"); s1.on(); s2.on(); s1.off(); s2.off(); s2.on(); s1.on(); } } |
如代码所见,我们的需求是开关类共享同一个状态(开关的开或者关),不同于银行的例子,所以我们要在开关类中定义静态的开关状态实现开关状态的共享,从而实现两个开关的状态共享,同时打开或者关闭,这种情况(多个环境对象可能需要共享同一个状态)依然试用与状态模式来解决。
但是上面咱们所写的状态模式代码还存在什么问题呢?是不是每个具体状态类中都有一个状态转换的代码?如果在加一种状态的话,是不是每个状态转换的代码都需要修改?显然不符合开放封闭原则,关于开放封闭原则您可以参考老四的这篇文章《浅析设计模式第四章之开放-封闭原则》,所以在银行例子当中,我们需要将stateCheck方法也要抽象出来就完美了。就是老四之前提及到的,将状态检查方法提取出来放入到环境类当中,这样就可以了,代码就不演示了。
状态模式的优点总结:
- 将与特定状态相关的行为局部化,并且将不同状态的行为分割开来,消除庞大的条件分支语句。这样我们通过将特定的状态相关的行为都放入一个对象中,由于所有与状态相关的代码都存在于具体状态实现类当中,所以通过定义新的子类就可以很容易地增加新的状态和对象行为转换。
- 状态的集中管理,封装状态的转换规则,这样我们只需要注入不同状态的状态对象到环境类当中就能实现对象的不同行为。
- 状态共享。减少系统对象。
缺点总结:
- 有多少种状态就有多少个具体的状态类,系统开销大了
- 状态对象与环境类对象构造混杂,复杂点状态对象行为会导致代码结构混乱。
- 状态模式并不是完善的满足开放-封闭原则,无论是新增/修改状态都需要修改对应类的源码,不过总比错综复杂的条件分支语法强太多。
项目完整源码文末自助获取下载。注册之后即可下载,不要钱的,一个注册只是提高一下学习的门槛,老四没那么势力!!
更博不易,喜欢的老铁有能力烦请赞助盒烟钱,点我去赞助。抱拳。
资源下载
隐藏内容:******,购买后可见!
下载价格:0 G币
您需要先登录后,才能购买资源
欢迎访问高老四博客(glorze.com),本站技术文章代码均为老四亲自编写或者借鉴整合,其余资源多为网络收集,如涉及版权问题请与站长联系。如非特殊说明,本站所有资源解压密码均为:glorze.com。