### 簡介
在一個應用的生命周期里,大部分時間都花在了向現有代碼庫增加功能,而非一直從零開始寫新功能。你可能已經意識到了,這會是一個繁瑣且令人痛苦的過程。無論何時你修改代碼,都有可能引入新的bug,或者破壞原有的舊功能。理想情況下,我們應該可以像寫全新的代碼一樣來快速修改現有的代碼。如果采用開放封閉原則來正確設計我們的應用程序,那么這是可以做到的!
> 開放封閉原則,又稱開閉原則,規定代碼對擴展是開放的,對修改是封閉的。
### 實戰
為了演示開放封閉原則,我們來繼續編寫前面實現的 `OrderProcecssor` 類。考慮下面的 `process` 方法:
```php
$recent = $this->orders->getRecentOrderCount($order->account);
if($recent > 0)
{
throw new Exception('Duplicate order likely.');
}
```
這段代碼的可讀性很好,由于我們使用了依賴注入,也很容易測試。但是,如果我們關于訂單驗證的業務規則改變了呢?如果我們又有新的規則了呢?更進一步,如果隨著我們的業務發展,要增加一大堆新規則呢?那我們的 `process` 方法將很快變成難以維護的意大利面條式代碼。因為這段代碼必須隨著每次業務規則的改變而改變,它對修改是開放的,這違反了開放封閉原則。記住,我們希望代碼對擴展開放,而不是修改。
所以我們要避免把訂單驗證代碼直接寫在 `process` 方法里面,下面我們來定義一個新的接口 `OrderValidator`:
```php
interface OrderValidatorInterface
{
public function validate(Order $order);
}
```
接下來,我們來定義一個防止重復訂單的實現類:
```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.');
}
}
}
```
很好!我們封裝了一個小巧的、可測試的單一業務規則。然后再創建一個用來驗證賬號是否停用的實現類:
```php
class SuspendedAccountValidator implements OrderValidatorInterface
{
public function validate(Order $order)
{
if($order->account->isSuspended())
{
throw new Exception("Suspended accounts may not order.");
}
}
}
```
現在,我們有兩個不同的類實現了 `OrderValidatorInterface` 接口。我們將在 `OrderProcessor` 中使用它們。我們只需簡單注入一個驗證器數組到訂單處理器實例,今后就可以從代碼庫中輕松添加和移除驗證規則。
```php
class OrderProcessor
{
public function __construct(BillerInterface $biller, OrderRepository $orders, array $validators = array())
{
$this->biller = $bller;
$this->orders = $orders;
$this->validators = $validators;
}
}
```
接下來,我們只要在 `process` 方法里面遍歷這個驗證器數組即可:
```php
public function process(Order $order)
{
foreach($this->validators as $validator)
{
$validator->validate($order);
}
// Process valid order...
}
```
最后,我們在服務容器中注冊 `OrderProcessor` 類:
```php
$this->app->bind('OrderProcessor', function($app)
{
return new OrderProcessor(
$app->make('BillerInterface'),
$app->make('OrderRepository'),
[
$app->make('RecentOrderValidator'),
$app->make('SuspendedAccountValidator'),
]
);
});
```
有了這些修改之后,我們就可以實現在不修改任何一行現有代碼的情況下添加和移除新的驗證規則。每一個新的驗證規則就是 `OrderValidatorInterface` 接口的一個實現類,然后將其注冊到服務容器里。不必再對那個又龐大又笨重的`process` 方法做單元測試了,我們現在可以單獨測試每一個驗證規則。現在,我們的代碼對擴展是開放的,對修改是封閉的。
> 要小心那些泄露實現細節的依賴。當一個依賴的實現需要改變時,不應該要求它的調用者做任何修改。如果需要調用者進行修改的話,往往意味著該依賴「泄露」了實現的細節。當你的抽象泄露時,開放封閉原則就不管用了。
在我們繼續學習前,需要提醒一下,這些原則不是法律。并不是規定你應用中的每一塊代碼都必須是「可插拔」的。例如,對于一個僅僅從 MySQL 數據庫中檢索幾條記錄的小應用程序而言,沒必要去嚴格遵守每一條你所知道的設計原則。不要盲目的應用設計原則,那樣你會創建出一個「過度設計」的繁瑣系統。要知道,很多設計原則是為了解決大型應用程序的通用架構問題而產生的。不過,話雖如此,也不要以此為借口在需要用到設計模式時偷懶!