### 簡介
在整個「SOLID」原則概述的旅途中,我們到達最后一站了!最后一個原則是依賴反轉原則,它規定高層次的代碼不應該依賴低層級的代碼。換句話說,高層次的代碼應該依賴抽象接口,抽象接口就像是「中間人」一樣,負責連接著高層次和低層次代碼。這個原則的另一層意思是,抽象接口不應該依賴具體實現,但具體實現應該依賴抽象接口。如果這些理論聽起來讓你極端困惑,別擔心,接下來我們會將圍繞這兩個方面將這個原則詳細介紹給你。
### 實踐
如果你已經讀過了本書前面幾個章節,你其實就已經很好地掌握了依賴反轉原則!為了說明本原則,我們來看看下面這個類:
```php
class Authenticator {
public function __construct(DatabaseConnection $db)
{
$this->db = $db;
}
public function findUser($id)
{
return $this->db->exec('select * from users where id = ?', array($id));
}
public function authenticate($credentials)
{
// Authenticate the user...
}
}
```
你可能猜到了,`Authenticator` 是用來查找和認證用戶的。我們來看一下它的構造函數,你會發現它使用了類型提示,要求傳入一個`DatabaseConnection` 對象,所以該認證器和數據庫被緊密地耦合在一起,并且用戶數據只能通過支持 SQL 的關系型數據庫提供。此外,我們的高層次代碼(`Authenticator`)直接依賴低層次代碼(`DatabaseConnection`)。
首先,我們需要來談談「高層次代碼」和「低層次代碼」。低層次代碼用于實現一些底層的基本操作,比如從磁盤讀文件、操作數據庫等。高層次代碼用于封裝復雜的業務邏輯并且依靠低層次代碼來實現功能,但不能直接和低層次代碼耦合在一起。換句話說,高層次代碼需要依賴低層次代碼的頂層抽象,比如接口。不僅如此,低層次代碼也應當依賴抽象接口。所以,我們來寫個可以在 `Authenticator` 中使用的接口:
```php
interface UserProviderInterface
{
public function find($id);
public function findByUsername($username);
}
```
接下來我們將該接口注入到 `Authenticator` 里面:
```php
class Authenticator {
public function __construct(UserProviderInterface $users, HasherInterface $hash)
{
$this->hash = $hash;
$this->users = $users;
}
public function findUser($id)
{
return $this->users->find($id);
}
public function authenticate($credentials)
{
$user = $this->users->findByUsername($credentials['username']);
return $this->hash->make($credentials['password']) == $user->password;
}
}
```
做了以上改動后,`Authenticator` 現在依賴于兩個高層級的抽象:`UserProviderInterface` 和 `HasherInterface`。我們可以向`Authenticator` 注入這兩個接口的任何實現類。例如,如果我們的用戶存儲在 Redis 里面,我們只需寫一個 `RedisUserProvider` 來實現`UserProviderInterface` 接口即可。`Authenticator` 不再直接依賴低層次的存儲操作了。
此外,我們的低層次代碼現在依賴高層次的 `UserProviderInterface` 抽象,因為它實現了接口本身:
```php
class RedisUserProvider implements UserProviderInterface
{
public function __construct(RedisConnection $redis)
{
$this->redis = $redis;
}
public function find($id)
{
$this->redis->get('users:'.$id);
}
public function findByUsername($username)
{
$id = $this->redis->get('user:id:'.$username);
return $this->find($id);
}
}
```
> 反轉的思想:使用這一原則會反轉很多開發者設計應用的方式。不再將高層次代碼直接和低層次代碼以「自上而下」的方式耦合在一起,這個原則規定不論高層級還是低層次代碼都要依賴于一個高層次的抽象,從而使得低層次代碼依賴于高層次代碼的需求抽象。
在我們沒有反轉 `Authenticator` 的依賴之前,它除了使用數據庫存儲系統別無選擇。如果我們改變了存儲系統,`Authenticator` 也需要被修改,這就違背了開放封閉原則。我們又一次看到,這些設計原則通常一榮俱榮一損俱損。
強制讓 `Authenticator` 依賴一個存儲層的抽象接口之后,我們就可以通過任何實現了 `UserProviderInterface` 接口的存儲系統來使用它,而且不用對 `Authenticator` 本身做任何修改。傳統的依賴關系鏈已經被反轉了,代碼變得更靈活,可以更好的擁抱變化!