Open Closed Principle 开放封闭原则
Introduction 介绍
Over the life time of an application, more time is spent adding to the existing codebase rather than constantly adding new features from scratch. As you are probably aware, this can be a tedious and frustrating process. Anytime you modify code, you risk introducing new bugs, or breaking old functionality completely. Ideally, we should be able to modify an existing codebase as quickly and easily as writing brand new code. If we correctly design our application according to the Open Closed principle, we can just do that!
在一个应用的生命周期里,大部分时间都花在了向现有代码库增加功能,而非一直从零开始写新功能。正像你所想的那样,这会是一个繁琐且令人痛苦的过程。当你修改代码的时候,你可能引入新的程序错误,或者将原来管用的功能搞坏掉。理想情况下,我们应该可以像写全新的代码一样,来快速且简单的修改现有的代码。只要采用开放封闭原则来正确的设计我们的应用程序,那么这是可以做到的!
Open Closed Principle 开放封闭原则
The Open Closed principle of SOLID design states that code is open for extension but closed for modification.
开放封闭原则规定代码对扩展是开放的,对修改是封闭的。
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
方法:
<!-- lang:php -->
$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
:
<!-- lang:php -->
interface OrderValidatorInterface {
public function validate(Order $order);
}
Next, let's define an implementation that protects against duplicate orders:
下一步我们来定义一个实现接口的类,来预防重复订单:
<!-- lang:php -->
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:
很好!我们封装了一个小巧的、可测试的单一业务逻辑。咱们来再创建一个来验证账号是否停用吧:
<!-- lang:php -->
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
里面使用它们。我们只需简单的将一个验证器数组注入进订单处理器实例中。这将使我们以后修改代码时能轻松的添加和删除验证器规则。
<!-- lang:php -->
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
方法里面循环这个验证器数组即可:
<!-- lang:php -->
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
类:
<!-- lang:php -->
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检索几条记录的小应用程序,不值得去严格遵守每一条你想到的设计原则。不要盲目的应用设计原则,那样你会造出一个“过度设计”的繁琐的系统。记住这些设计原则是用来解决通用的架构问题,制造大型容错能力强的应用。我就这么一说,你可别把它当作懒惰的借口!