在介绍状态模式之前,我们先看个简单的例子
比如我们正常的网上购物的订单,对于订单它有许多个不同的状态,在不同状态下会有不同的行为能力,如状态有待支付、已支付待发货、待收货、订单完成等等,在整个订单流转过程中,由不同的事件行为导致它状态的变更,可以看下它简单的状态图
这些状态虽然不同,但是都是属于订单的状态,对应的行为也是订单的行为,在编码的过程中,一种写法是将这些行为(方法)都写到订单这个类或者类似OrderService这种类中,如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class OrderService { private OrderDao orderDao;
public void pay(Long orderId) { Order order = orderDao.findById(orderId); if (order.getStatus() == OrderStatus.WAIT_PAY) { } else if (order.getStatus() == OrderStatus.PAYING) { } else if (order.getStatus() == OrderStauts.PART_PAID) { } else { } } }
|
这样每次进行操作时,我们要先进行一下当前状态的判断,如用户支付时,我们要判断一下当前订单的状态,如果是待支付,则进行支付操作后,将状态改为已支付;如果当前状态是已支付时,就要提示用户当前订单已支付,不能再次支付等等,如果订单状态比较复杂时就会导致这个类中充斥大量的if
、switch
等判断逻辑,维护不便
而状态模式则提供了另一种解决方案,它将与特定状态相关的行为局部化,也使得状态转换现实化,下面我我们来具体看一下
状态模式
状态模式的目的:允许一个对象在其内部状态改变时改变它的行为,使得对象看起来似乎修改了它的类
我们先看一下状态模式的类图
Context为客户感兴趣的接口,State为状态,ConcreteState为具体状态子类,每一个子类实现一个与Context的一个状态相关的行为
Context将与状态相关的请求委托给对应的ConcreteState对象处理,同时可将自身作为一个参数传递给该请求对应的状态对象
下面我们使用《敏捷软件开发:原则、模式与实践(C#版)》中的闸机例子来分析一下
地铁闸机:
状态 |
行为(事件) |
响应 |
关闭 |
投币 |
开门 |
关闭 |
(强行)通过 |
报警 |
开启 |
通过 |
关门 |
开启 |
投币 |
致谢 |
具体代码
状态接口
1 2 3 4 5
| public interface TurnstileState { void coin(Turnstile t); void pass(Turnstile t); }
|
闸机控制器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class TurnstileController { public void unlock() { } public void alarm() { }
public void thankyou() { }
public void lock() { } }
|
门关闭状态类
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class LockedTurnstileState implements TurnstileState { @Override public void coin(Turnstile t) { t.setUnlock(); t.unlock(); } @Override public void pass(Turnstile t) { t.alarm(); } }
|
门开启时状态类
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class UnLockedTurnstileState implements TurnstileState { @Override public void coin(Turnstile t) { t.thankyou(); } @Override public void pass(Turnstile t) { t.setLocked(); t.lock(); } }
|
闸机功能实现
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
| public class Turnstile { private static TurnstileState lockedState = new LockedTurnstileState(); private static TurnstileState unlockedState = new UnLockedTurnstileState(); private TurnstileController turnstileController; private TurnstileState state = lockedState;
public Turnstile(TurnstileController turnstileController) { this.turnstileController = turnstileController; } public void coin() { state.coin(this); } public void pass() { state.pass(this); } public void setLocked() { state = lockedState; } public void setUnlock() { state = unlockedState; } public boolean isLocked() { return state == lockedState; } public boolean isUnlocked() { return state == unlockedState; } void thankyou() { turnstileController.thankyou(); } void alarm() { turnstileController.alarm(); } void lock() { turnstileController.lock(); } void unlock() { turnstileController.unlock(); } }
|
以上基本就是状态模式的主要使用方式,相比起聚在一起的判断代码,它将不同状态的行为分割开来,让每一个状态相关的代码都集中在对应的State状态类中,如果有新的状态子类,可以很容易的增加扩展,而不需要改动之前的代码(符合开闭原则)
当然,这样带来的一个副作用就是增加了子类的数目,但是如果状态复杂时,这个成本还是值得的
最后我们总结一个状态模式的适用场景
- 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为
- 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常,有多个操作包含这一相同的条件结构。State模式将每一个条件分支放入一个独立的类中。这使得你可以根据自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化
参考资料
- 《设计模式-可复用面向对象软件的基础》
- 《敏捷软件开发:原则、模式与实践(C#版)》