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 UserProviderInterface contract. 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 Authenticator itself. The conventional dependency chain has been inverted and our code is much more flexible and welcoming of change!

通过强制让 Authenticator 依赖着一个存储抽象层,我们就可以使用任何实现了 UserProviderInterface 接口的存储系统,且不用对 Authenticator 本身做任何修改。传统的依赖关系链已经被反转了,代码变得更灵活,更加无惧变化!

results matching ""

    No results matching ""