Dependency Inversion Principle 依賴反轉原則

Dependency Inversion Principle 依賴反轉原則

Introduction 介紹

We have reached our final destination in our overview of the five SOLID design principles! The final principle is the Dependency Inversion principle, and it states that high-level code should not depend on low-level code. Instead, high-level code should depend on an abstraction layer that serves as a “middle-man” between the high and low-level code. A second aspect to the principle is that abstractions should not depend upon details, but rather details should depend upon abstractions. If this all sounds extremely confused, don’t worry. We’ll cover both aspects of this principle below.

在整個“堅實”原則概述的旅途中,我們到達最後一站了!最後的原則是依賴反轉原則,它規定高等級的代碼不應該依賴低等級的代碼。首先,高等級的代碼應該依賴着抽象層,抽象層就像是“中間人”一樣,負責連接着高等級和低等級的代碼。其次,抽象定義不應該依賴着具體實現,但具體實現應該依賴着抽象定義。如果這些東西讓你極端困惑,別擔心。接下來我們會將這兩方面統統介紹給你。

Dependency Inversion Principle 依賴反轉原則

This principle states that high-level code should not depend on low-level code, and that abstractions should not depend upon details.

該原則要求高等級代碼不應該依賴低等級代碼,抽象定義不應該依賴具體實現。

In Action 實踐

If you have already read prior chapters of this book, you already have a good grasp of the Dependency Inversion principle! To illustrate the principle, let’s consider the following class:

如果你已經讀過了本書前面幾個章節,你就已經很好掌握了依賴反轉原則!爲了說明本原則,讓我們考慮下面這個類:

    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...
        }
    }

As you might have guessed, the Authenticator class is responsible for finding and authenticating users. Let’s examine the constructor of this class. You will see that we are type-hinting a DatabaseConnection instance. So, we’re tightly coupling our authenticator to the database, and essentially saying that users will always only be provided out of a relational SQL database. Furthermore, our high-level code (the Authenticator) is directly depending on low-level code (the DatabaseConnection).

你可能猜到了,Authenticator就是用來查找和驗證用戶的。繼續研究它的構造函數。我們發現它使用了類型提示,要求傳入一個DatabaseConnection對象,所以該驗證類和數據庫被緊密的聯繫在一起。而且基本上講,這個數據庫還只能是關係數據庫。從而可知,我們的高級代碼(Authenticator)直接的依賴着低級代碼(DatabaseConnection)。
First, let’s discuss “high-level” and “low-level” code. Low-level code implements basic operations like reading files from a disk, or interaction with a database. High-level code encapsulates complex logic and relies on the low-level code to function, but should not be directly coupled to it. Instead, the high-level code should depend on an abstraction that sits on top of the low-level code, such as an interface. Not only that, but the low-level code should also depend upon an abstraction. So, let’s write an interface that we can use within our Authenticator:

首先我們來談談“高級代碼”和“低級代碼”。低級代碼用於實現基本的操作,比如從磁盤讀文件,操作數據庫等。高級代碼用於封裝複雜的邏輯,它們依靠低級代碼來達到功能目的,但不能直接和低級代碼耦合在一起。取而代之的是高級代碼應該依賴着低級代碼的頂層抽象,比如接口。不僅如此,低級代碼也應當依賴着抽象。 所以我們來寫個Authenticator可以用的接口:

    interface UserProviderInterface {
        public function find($id);
        public function findByUsername($username);
    }

Next let’s inject an implementation of this interface into our Authenticator:

接下來我們將該接口注入到Authenticator裏面:

    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;
        }
    }

After making these changes, our Authenticator now relies on two high-level abstractions: UserProviderInterface and HasherInterface. We are free to inject any implementation of these interfaces into the Authenticator. For example, if our users are now stored in Redis, we can write a RedisUserProvider which implements the UserProviderInterfacecontract. The Authenticator is no longer depending directly on low-level storage operations.

做了這些小改動後,Authenticator現在依賴於兩個高級抽象:UserProviderInterface和HasherInterface。我們可以向Authenticator自由的注入這倆接口的任何實現類。比如,如果我們的用戶存儲在Redis裏面,我們只需寫一個RedisUserProvider來實現UserProviderInterface接口即可。Authenticator不再依賴着具體的低級別的存儲操作了。

Furthermore, our low-level code now depends on the high-level UserProviderInterface abstraction, since it implements the interface itself:

此外,由於我們的低級別代碼實現了UserProviderInterface接口,則我們說該低級代碼依賴着這個接口。

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

Inverted Thinking 反轉的思維

Applying this principle inverts the way many developers design applications. Instead of coupling high-level code directly to low-level code in a “top-down” fashion, this principle states that both high and low-level code depend upon a high-level abstraction.

貫徹這一原則會反轉好多開發者設計應用的方式。不再將高級代碼直接和低級代碼以“自上而下”的方式耦合在一起,這個原則提出無論高級還是低級代碼都要依賴於一個高層次的抽象。

Before we “inverted” the dependencies of our Authenticator, it could not be used with anything other than a database storage system. If we changed storage system, our Authenticator would also have to be modified, in violation of the Open Closed principle. Again, as we have seen before, multiple principles usually stand or fall together.

在我們沒有反轉Authenticator的依賴之前,它除了使用數據庫存儲系統別無選擇。如果我們改變了存儲系統,Authenticator也需要被修改,這就違背了開放封閉原則。我們又一次看到,這些設計原則通常一榮俱榮一損俱損。

After forcing the Authenticator to depend upon an abstraction over the storage layer, we can use it with any storage system that implements our UserProviderInterface contract without any modifications to the Authenticatoritself. The conventional dependency chain has been inverted and our code is much more flexible and welcoming of change!

通過強制讓Authenticator依賴着一個存儲抽象層,我們就可以使用任何實現了UserProviderInterface接口的存儲系統,且不用對Authenticator本身做任何修改。傳統的依賴關係鏈已經被反轉了,代碼變得更靈活,更加無懼變化!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章