设计模式历险记之状态模式

状态模式: 当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。

我们先来看看它的结构图:

通过结构图可以知道,状态模式存在一个维护当前状态实体的上下文类,它和状态类是一种聚合关系。
然后有一个状态抽象类以及对应具体的状态实体。

接下来举一个小小的栗子吧。

现在有一个场景是你在做一个电商网站的订单流程。
都知道,订单从下单到完成中间会有很多的状态,每种状态对与订单来说又有不同的操作。

我们先用普通的面向对象的方式来实现它:

<?php
class order{

    //订单的不同状态
    const NEW_ORDER = 1;
    const PAY_ORDER = 2;
    const SHIPMENTS_ORDER = 3;
    const FINISH_ORDER = 4;

    //订单当前状态
    protected $status;

    //给订单设置状态
    public function setStatus($status){
        $this->status = $status;
    }

    //当前状态下的订单的不同操作
    public function ope(){
        switch($this->status){
            case self::NEW_ORDER:
                echo '新订单';
                break;

            case self::PAY_ORDER:
                echo '订单已付款';
                break;

            case self::SHIPMENTS_ORDER:
                echo '订单已发货';
                break;

            case self::FINISH_ORDER:
            default:
                echo '订单已完成';
                break;

        }
    }
}

$order = new order();

$order->setStatus(1);
$order->ope();

这种代码确实也是通过OOP思想来写出来的,但是它却有着很大的不足,
我们仔细观察订单的操作方法 ope() 里面用了 switch 条件分支来区分不同的状态应该调用不同的操作,
它是能够实现功能。但是,如果订单的状态足够多,对应 ope 中的 switch 也会变得足够多,
现在如果订单的状态增加了或者是减少了,这个时候我们就必须将这段代码拿来开刀,
这样是不是就违背了 开放-关闭 原则,并使维护出错的概率大大增加。所以,这种写法不可取。

下面用状态模式来重写这段逻辑:

<?php
class order{

    //订单当前状态,存储状态类
    protected $status;

    //订单状态初始化
    public function __construct(){
        $this->status = new newOrder();
    }

    //设置订单的状态
    public function setStatus(orderStatusToAbstract $orderStatus){
        $this->status = $orderStatus;
    }

    //订单的操作
    public function ope(){
        $this->status->ope($this);
    }

}

//抽象状态类
abstract class orderStatusToAbstract{
    abstract function ope(order $order);
}

//新订单状态
class newOrder extends orderStatusToAbstract{
    public function ope(order $order){
        echo '新订单具有的操作'.PHP_EOL;
        $order->setStatus((new payOrder()));
    }
}

//以付款订单状态
class payOrder extends orderStatusToAbstract{
    public function ope(order $order){
        echo '付款订单具有的操作'.PHP_EOL;
        $order->setStatus((new shipmentsOrder()));
    }
}

//运输中订单状态
class shipmentsOrder extends orderStatusToAbstract{
    public function ope(order $order){
        echo '运输订单具有的操作'.PHP_EOL;
        //操作完成后订单状态的变化
        $order->setStatus((new finishOrder()));
    }
}

//完成订单状态
class finishOrder extends orderStatusToAbstract{
    public function ope(order $order){
        echo '以完成订单具有的操作';
    }
}

$order = new order();

$order->ope();

$order->ope();

$order->ope();

通过状态模式重构后的代码将初始代码中 switch 中的逻辑判断拆分出来成单独的状态类,不同的状态类有不同的操作。
这样,无论订单有多少的状态,无论以后怎么添加状态或者使修改状态,我们只需要修改对应独立的类就好,而不会影响到其它状态类。
满足关闭-开放原则,使代码维护困难度和出错概率大大减少。

总结一下吧:

  • 先来对文章开头定义(晦涩难懂)来谈谈我的理解吧:

当一个对象的内在状态改变时允许改变其行为:一个对象的状态发生变化,那么这个对象对应的行为也应该同时发生变化,状态模式的做法就使将这个类的状态和对应状态的操作拆分为单独的状态类,然后将状态类注入到对象中,这个时候对象的状态变化了,同时对应的操作方式也变化。
这个对象看起来像是改变了其类:对应的主体类的操作是通过调取注入的状态类的操作,调用操作方式和形式都一样,但是具体的结果却不一样,所以看起来像是改变了自己。

  • 在哪种情况下用装填模式呢?

如果某对象有很多状态,并且对象在不同的状态下有不同的操作。
常规的做法都是通过大量的逻辑判断分支类区分不同状态下的不同操作,这样做有一个很大的问题就是后期维护成本增加,可拓展性不好,易出错。
如果我们使用状态模式,通过将不同状态以及状态下的操作单独以一个类的形式拿出来,这样就会很好的避免上面所说的问题。

疑问自提自答:

  • 状态模式和策略模式表面上很像,但是它们内在是什么不同呢?

策略模式主要是在不同场景对不同的策略的相互替换;在选择不同策略的时候,还是很多逻辑分支判断,只是将逻辑分支封装了应用策略的上下文类中。
状态模式的目的不是对不同的状态的相互替换,而是一种流程,在这个流程中的不同时刻会有不同的状态下的不同操作;并且基本上不会存在很多的逻辑分支判断。

Snail's Blog
请先登录后发表评论
  • 最新评论
  • 总共0条评论
  • 本博客使用免费开源的 laravel-bjyblog v5.5.1.3 -develop 搭建 © 2014-2018 www.snail-c.cn 版权所有 ICP证:蜀ICP备18023253号-1
  • 联系邮箱:459921737@qq.com