### 簡介
羅伯特·C·馬丁在 21 世紀早期引入了名為「SOLID」的設計原則,指代了面向對象編程和面向對象設計的五個基本原則:
- 單一職責原則(**S**ingle Responsibility Principle)
- 開放封閉原則(**O**pen Closed Principle)
- 里氏替換原則(**L**iskov Substitution Principle)
- 接口隔離原則(**I**nterface Segregation Principle)
- 依賴反轉原則(**D**ependency Inversion Principle)
下面我們將深入探索以上每個設計原則,并且通過示例代碼來闡述各個原則。我們將看到,每個原則之間都有聯系。如果其中一個原則沒有被遵循,那么其他大部分(可能不會是全部)的原則也會出問題。
### 實戰
單一職責原則規定一個類有且僅有一個理由使其改變。換句話說,一個類的邊界和職責應當是十分狹窄且集中的。我們之前就提到過,在類的職責問題上,無知是福。一個類應當做它該做的事,并且不應當被它的任何依賴的變化所影響。
考慮下面這個類:
```php
class OrderProcessor
{
public function __construct(BillerInterface $biller)
{
$this->biller = $biller;
}
public function process(Order $order)
{
$recent = $this->getRecentOrderCount($order);
if($recent > 0)
{
throw new Exception('Duplicate order likely.');
}
$this->biller->bill($order->account->id, $order->amount);
DB::table('orders')->insert(array(
'account' => $order->account->id,
'amount' => $order->amount,
'created_at'=> Carbon::now()
));
}
protected function getRecentOrderCount(Order $order)
{
$timestamp = Carbon::now()->subMinutes(5);
return DB::table('orders')->where('account', $order->account->id)
->where('created_at', '>=', $timestamps)
->count();
}
}
```
上面這個類的職責是什么?很顯然,顧名思義,它是用來處理訂單的。但是,通過 `getRecentOrderCount` 方法,這個類又有了在數據庫中審查某個帳號的訂單歷史以便判斷是否有重復訂單的職責。這個額外的驗證職責意味著當我們的存儲方式改變或者訂單驗證規則改變時,這個訂單處理器也要跟著改變。
我們應該將這個驗證職責提取出來放到其它類中,比如 `OrderRepository` 類:
```php
class OrderRepository
{
public function getRecentOrderCount(Account $account)
{
$timestamp = Carbon::now()->subMinutes(5);
return DB::table('orders')->where('account', $account->id)
->where('created_at', '>=', $timestamp)
->count();
}
public function logOrder(Order $order)
{
DB::table('orders')->insert(array(
'account' => $order->account->id,
'amount' => $order->amount,
'created_at'=> Carbon::now()
));
}
}
```
然后,我們可以將這個倉庫類注入到 `OrderProcessor` 里,以便分擔后者對賬戶訂單歷史進行檢查的責任:
```php
class OrderProcessor {
public function __construct(BillerInterface $biller, OrderRepository $orders)
{
$this->biller = $biller;
$this->orders = $orders;
}
public function process(Order $order)
{
$recent = $this->orders->getRecentOrderCount($order->account);
if($recent > 0)
{
throw new Exception('Duplicate order likely.');
}
$this->biller->bill($order->account->id, $order->amount);
$this->orders->logOrder($order);
}
}
```
現在,我們已經提取出了收集訂單數據的職責,當獲取和記錄訂單的方法改變時,就不再需要修改 `OrderProcessor` 這個類了。我們的類的職責變得更加專注和集中,同時也讓代碼變得更簡潔、更優雅、更容易維護。
需要注意的是,單一職責原則的核心并不僅僅是讓代碼變短,而是要寫出職責更加明確、方法更加內聚的類,所以要確保類里面所有的方法都隸屬于該類的職責之內。在構建一個小巧、清晰且職責明確的類庫以后,我們的代碼會更加解耦,更容易測試,并且更易于修改。