今天翻了翻《领域驱动设计与模式实战》,里面详细讲解了“状态模式”,说来我对它并不陌生,几年前,我在看《Java与模式》的时候就仔细研究过,不过这么多年来却从没在实战中应用过,并不是没有遇到合适的场景,即便在前几天结束的一个项目中,还涉及订单状态来着,可我却是用最俗的if/else编码的,书算是白看了,为了不让悲剧重演,我决定重新温习一下状态模式,加深一下印象。


无图无真相,下面看看订单在生命周期里的状态迁移:


java 单据号按时间生成订单号_生命周期



如图所示:订单状态有New Order,Registered,Granted,Shipped,Invoiced,Cancelled,相当复杂,在不同的状态执行操作时会产生不同的影响,比如说我们要执行AddOrderLine的话,要判断订单状态,如果是Registered或Granted状态的话,订单状态会变成New Order,如果是New Order状态的话,则状态保持不变,如果是其它状态的话,则不允许AddOrderLine操作。


如果我们不使用状态模式的话,那么代码里免不了充斥着if/else,所有涉及状态的操作都会被拖累,这还不算,一旦要是加入新的状态(比如说加入一个退货状态),你不得不修改原有的代码,挨个加上elseif,方法代码会变得越来越冗长,开闭原则算是没戏了,维护这样的代码,早晚有一天会崩溃的。


下面看看状态模式是如何解决此类问题的,见UML图:


java 单据号按时间生成订单号_策略模式_02




引入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“。