开放封闭原则

简介

在一个应用的生命周期里,大部分时间都花在了向现有代码库增加功能,而非一直从零开始写新功能。你可能已经意识到了,这会是一个繁琐且令人痛苦的过程。无论何时你修改代码,都有可能引入新的bug,或者破坏原有的旧功能。理想情况下,我们应该可以像写全新的代码一样来快速修改现有的代码。如果采用开放封闭原则来正确设计我们的应用程序,那么这是可以做到的!

开放封闭原则,又称开闭原则,规定代码对扩展是开放的,对修改是封闭的。

实战

为了演示开放封闭原则,我们来继续编写前面实现的 OrderProcecssor 类。考虑下面的 process 方法:

$recent = $this->orders->getRecentOrderCount($order->account);

if($recent > 0)
{
    throw new Exception('Duplicate order likely.');
}

这段代码的可读性很好,由于我们使用了依赖注入,也很容易测试。但是,如果我们关于订单验证的业务规则改变了呢?如果我们又有新的规则了呢?更进一步,如果随着我们的业务发展,要增加一大堆新规则呢?那我们的 process 方法将很快变成难以维护的意大利面条式代码。因为这段代码必须随着每次业务规则的改变而改变,它对修改是开放的,这违反了开放封闭原则。记住,我们希望代码对扩展开放,而不是修改。

所以我们要避免把订单验证代码直接写在 process 方法里面,下面我们来定义一个新的接口 OrderValidator

interface OrderValidatorInterface 
{
    public function validate(Order $order);
}

接下来,我们来定义一个防止重复订单的实现类:

class RecentOrderValidator implements OrderValidatorInterface 
{

    public function __construct(OrderRepository $orders)
    {
        $this->orders = $orders;
    }

    public function validate(Order $order)
    {
        $recent = $this->orders->getRecentOrderCount($order->account);
        if ($recent > 0)
        {
            throw new Exception('Duplicate order likely.');
        }
    }
}

很好!我们封装了一个小巧的、可测试的单一业务规则。然后再创建一个用来验证账号是否停用的实现类:

class SuspendedAccountValidator implements OrderValidatorInterface 
{
    public function validate(Order $order)
    {
        if($order->account->isSuspended())
        {
            throw new Exception("Suspended accounts may not order.");
        }
    }
}

现在,我们有两个不同的类实现了 OrderValidatorInterface 接口。我们将在 OrderProcessor 中使用它们。我们只需简单注入一个验证器数组到订单处理器实例,今后就可以从代码库中轻松添加和移除验证规则。

class OrderProcessor 
{
    public function __construct(BillerInterface $biller, OrderRepository $orders, array $validators = array())
    {
        $this->biller = $bller;
        $this->orders = $orders;
        $this->validators = $validators;
    }
}

接下来,我们只要在 process 方法里面遍历这个验证器数组即可:

public function process(Order $order)
{
    foreach($this->validators as $validator)
    {
        $validator->validate($order);
    }

    // Process valid order...
}

最后,我们在服务容器中注册 OrderProcessor 类:

$this->app->bind('OrderProcessor', function($app)
{
    return new OrderProcessor(
        $app->make('BillerInterface'),
        $app->make('OrderRepository'),
        [
            $app->make('RecentOrderValidator'),
            $app->make('SuspendedAccountValidator'),
        ]
    );
});

有了这些修改之后,我们就可以实现在不修改任何一行现有代码的情况下添加和移除新的验证规则。每一个新的验证规则就是 OrderValidatorInterface 接口的一个实现类,然后将其注册到服务容器里。不必再对那个又庞大又笨重的process 方法做单元测试了,我们现在可以单独测试每一个验证规则。现在,我们的代码对扩展是开放的,对修改是封闭的。

要小心那些泄露实现细节的依赖。当一个依赖的实现需要改变时,不应该要求它的调用者做任何修改。如果需要调用者进行修改的话,往往意味着该依赖「泄露」了实现的细节。当你的抽象泄露时,开放封闭原则就不管用了。

在我们继续学习前,需要提醒一下,这些原则不是法律。并不是规定你应用中的每一块代码都必须是「可插拔」的。例如,对于一个仅仅从 MySQL 数据库中检索几条记录的小应用程序而言,没必要去严格遵守每一条你所知道的设计原则。不要盲目的应用设计原则,那样你会创建出一个「过度设计」的繁琐系统。要知道,很多设计原则是为了解决大型应用程序的通用架构问题而产生的。不过,话虽如此,也不要以此为借口在需要用到设计模式时偷懒!

上一篇: 单一职责原则

下一篇: 里氏替换原则