In Action 实践

To demonstrate the Open Closed principle, let's continue working with our OrderProcessor from the previous chapter. Consider the following section of the process method:

为了演示开放封闭原则,我们来继续编写上一章节的 OrderProcecssor。考虑下面的 process 方法:

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

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

This code is quite readable, and even easy to test since we are properly using dependency injection. However, what if our business rules regarding order validation change? What if we get new rules? In fact, what if, as our business grows, we get many new rules? Our process method will quickly grow into a beast of spaghetti code that is hard to maintain. Because this code must be changed each time our business rules change, it is open for modification and violates the Open Closed principle. Remember, we want our code to be open for extension, not modification.

这段代码可读性很高,且因为我们使用了依赖注入,变得很容易测试。然而,如果我们判断订单的规则改变了呢?如果我们又有新的规则了呢?更进一步,如果随着我们的业务发展,要增加一大堆新规则呢?那我们的 process 方法会很快变成一坨难以维护的浆糊。因为这段代码必须随着每次业务逻辑的改变而跟着改变,它对修改是开放的,这违反了开放封闭原则。记住,我们希望代码对扩展开放,而不是修改。

Instead of performing all of our order validation directly inside the process method, let's define a new interface: OrderValidator:

不必再把订单验证直接写在 process 方法里面,我们来定义一个新的接口:OrderValidator:

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

Next, let's define an implementation that protects against duplicate orders:

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

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.');
        }
    }
}

Great! Now we have a small, testable encapsulation of a single business rule. Let's create another implementation that verifies the account is not suspended:

很好!我们封装了一个小巧的、可测试的单一业务逻辑。咱们来再创建一个来验证账号是否停用吧:

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

Now that we have two different implementations of our OrderValidatorInterface, let's use them within our OrderProcessor. We'll simply inject an array of validators into the processor instance, which will allow us to easily add and remove validation rules as our codebase evolves.

现在我们有两个不同的类实现了OrderValidatorInterface接口。咱们将在OrderProcessor里面使用它们。我们只需简单的将一个验证器数组注入进订单处理器实例中。这将使我们以后修改代码时能轻松的添加和删除验证器规则。

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

Next, we can simply loop through the validators in the process method:

然后我们只要在 process 方法里面循环这个验证器数组即可:

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

    // Process valid order...
}

Finally, we will register our OrderProcessor class in the application IoC container:

最后我们在 IoC 容器里面注册 OrderProcessor 类:

App::bind('OrderProcessor', function()
{
    return new OrderProcessor(
        App::make('BillerInterface'),
        App::make('OrderRepository'),
        array(
            App::make('RecentOrderValidator'),
            App::make('SuspendedAccountValidator')
        )
    );
});

With these few changes, which took only minimal effort to build from our existing codebase, we can now add and remove new validation rules without modifying a single line of existing code. Each new validation rule is simply a new implementation of the OrderValidatorInterface, and is registered with the IoC container. Instead of unit testing a large, unwieldy process method, we can now test each validation rule in isolation. Our code is now open for extension, but closed for modification.

在现有代码里付出些小努力,做一些小改动之后,我们现在可以添加删除新的验证规则而不必修改任何一行现有代码了。每一个新的验证规则就是对 OrderValidatorInterface 的一个实现类,然后注册进IoC容器里。不必再为那个又大又笨的 process 方法做单元测试了,我们现在可以单独测试每一个验证规则。现在,我们的代码对扩展是开放的,对修改是封闭的。

Leaky Abstractions 抽象的漏洞

Watch out for dependencies that leak implementation details. An implementation change in a dependency should not require any changes by its consumer. When changes to the consumer are required, it is said that the dependency is "leaking" implementation details. When your abstractions are leaky, the Open Closed principle has likely been broken.

小心那些缺少实现细节的依赖(译者注:比如上面的 RecentOrderValidator )。当一个依赖的实现需要改变时,不应该要求它的调用者做任何修改。当需要调用者进行修改时,这就意味着该依赖遗漏了一些实现的细节。当你的抽象有漏洞的话,开放封闭原则就不管用了。

Before processing further, remember that this principle is not a law. It does not state that every piece of code in your application must be "pluggable". For instance, a small application that retrieves a few records out of a MySQL database does not warrant a strict adherence to every design principle you can imagine. Do not blindly apply a given design principle out of guilt as you will likely create an over-designed, cumbersome system. Keep in mind that many of these design principles were created to address common architectural problems in large, robust applications. That being said, don't use this paragraph as an excuse to be lazy!

在我们继续学习前,要记住这些原则不是法律。这不是说你应用中每一块代码都应该是“热插拔”式的。例如,一个仅仅从 MySQL 检索几条记录的小应用程序,不值得去严格遵守每一条你想到的设计原则。不要盲目的应用设计原则,那样你会造出一个“过度设计”的繁琐的系统。记住这些设计原则是用来解决通用的架构问题,制造大型容错能力强的应用。我就这么一说,你可别把它当作懒惰的借口!

results matching ""

    No results matching ""