今天翻了翻《领域驱动设计与模式实战》,里面详细讲解了“状态模式”,说来我对它并不陌生,几年前,我在看《Java与模式》的时候就仔细研究过,不过这么多年来却从没在实战中应用过,并不是没有遇到合适的场景,即便在前几天结束的一个项目中,还涉及订单状态来着,可我却是用最俗的if/else编码的,书算是白看了,为了不让悲剧重演,我决定重新温习一下状态模式,加深一下印象。
无图无真相,下面看看订单在生命周期里的状态迁移:
如图所示:订单状态有New Order,Registered,Granted,Shipped,Invoiced,Cancelled,相当复杂,在不同的状态执行操作时会产生不同的影响,比如说我们要执行AddOrderLine的话,要判断订单状态,如果是Registered或Granted状态的话,订单状态会变成New Order,如果是New Order状态的话,则状态保持不变,如果是其它状态的话,则不允许AddOrderLine操作。
如果我们不使用状态模式的话,那么代码里免不了充斥着if/else,所有涉及状态的操作都会被拖累,这还不算,一旦要是加入新的状态(比如说加入一个退货状态),你不得不修改原有的代码,挨个加上elseif,方法代码会变得越来越冗长,开闭原则算是没戏了,维护这样的代码,早晚有一天会崩溃的。
下面看看状态模式是如何解决此类问题的,见UML图:
引入OrderState对象,原有的SalesOrder对象中的方法,只要涉及状态的,如:AddOrderLine等,都委派给具体的OrderState对象处理,从而避免了if/else的坏味道,这也是多态的威力所在。说到这里,有些读者可能会把状态模式和策略模式搞混了,确实,它们很相像,策略模式同样可以替换掉代码里if/else的坏味道,至于二者的区别,主要在于状态模式中,对象有明显的状态迁移,比如说用户有登录状态,在登陆前是未登录状态,在登陆后是登录状态,这里有明显的状态迁移;至于策略模式,则主要是算法的分离,而不存在状态的迁移,比如说下馆子结账时,满100元九折,满200元八折,就是策略模式,这里两个打折策略只能选一个,非此即彼,不存在迁移的可能性,有时候饭馆会进行累计消费的优惠,可能这次打九折,下次打八折,看似状态迁移了,但顾客的多次就餐行为通常是独立的,所以这是两个对象生命周期中的状态,而非一个对象生命周期中的两次状态迁移。
回到前面的订单例子,看看当执行AddOrderLine时,订单如何从Registered状态迁移到NewOrder状态:
代码(at pastebin.com):
01 class SalesOrder
02 {
03 protected $state;
04
05 public function __construct()
06 {
07 $this->state = new Registered($this);
08 }
09
10 public function setState($state)
11 {
12 $this->state = $state;
13 }
14
15 public function AddOrderLine()
16 {
17 $this->state->AddOrderLine();
18 }
19
20 // ...
21 }
22
23 abstract class OrderState
24 {
25 protected $order;
26
27 public function __construct($order)
28 {
29 $this->order = $order;
30 }
31
32 public function AddOrderLine()
33 {
34 // ...
35 }
36
37 // ...
38 }
39
40 class NewOrder extends OrderState
41 {
42 public function AddOrderLine()
43 {
44 // ...
45 }
46 }
47
48 class Registered extends OrderState
49 {
50 public function AddOrderLine()
51 {
52 $this->order->setState(new NewOrder($this->order));
53 }
54
55 // ...
56 }
57
58 $salesOrder = new SalesOrder();
59
60 var_dump($salesOrder);
61
62 $salesOrder->AddOrderLine();
63
64 var_dump($salesOrder);
说明:订单状态对象的实例化必须在订单对象中完成,这是因为订单状态对象如果单独存在的话是没有意义的,它的生命周期依赖于订单对象。
如果有兴趣的可以继续参考”A Head Start on Domain-Driven Design Patterns“。