依赖反转原则
# The Dependency Inversion Principle 依赖反转原则
# Introduction 简介
依赖反转原则规定,高层的代码不应该依赖下一级的代码。首先,高层的代码应该依赖着抽象层,抽象层就像是“中间人”一样,负责连接着高层代码和下一级代码。其次,抽象定义时不应该依赖着具体实现,但具体实现应该依赖着抽象定义。
# 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 实践
首先,考虑下面这个类:
class Authenticator
{
public function __construct(DatabaseConnection $db)
{
$this->db = $db;
}
public function findUser($id)
{
return $this->db->exec('select * from users from users where id=?', array($id));
}
public function authenticate($credentials)
{
// 认证客户...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
首先我们来谈谈高层代码和低层代码。低层代码用于实现基本的操作,比如从磁盘读文件,操作数据库等。高层代码用于封装复杂的逻辑,它们依靠低层代码来达到功能目的,但不能直接和低层代码耦合
在一起。
取而代之的是高层代码应该依赖着低层代码的顶层抽象,比如接口。不仅如此,低层代码也应当依赖着抽象
。所以我们来写个Authenticator
可以用的接口:
interface UserProviderInterface
{
public function find($id);
public function findByUsername($username);
}
2
3
4
5
接下来我们将该接口注入到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($crendentials['password']) == $user->password;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
做了这些小改动后,Authenticator
现在依赖于两个高层抽象:UserProviderInterface
和HasherInterface
。我们可以向Authenticator
自由的注入这两接口的任何实现类。比如,我们的用户存储在 Redis
里面,我们只需写一个RedisUserProvider
来实现 UserProviderInterface
接口即可。Authenticator
不再依赖着具体的低层实现类的存储操作了。
此外,由于我们的低层代码实现了UserProviderInterface
接口,则我们说该低层代码依赖着这个接口:
class RedisUserProviderInterface 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('users:id:'.$username);
return $this->find($id);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 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.
在运用依赖反转这一原则来进行开发设计时,会颠覆大多数开发者一般的设计方式。不再将高层代码直接和低层代码以‘自上而下’的方式耦合在一起,这个原则要求:无论是高层代码还是低层代码都要依赖于一个抽象,而不是具体实现。
在我们没有反转Authenticator
的依赖之前,它除了使用数据库存储系统别无选择。如果我们改变了存储系统,Authenticator
也需要被修改,这就违背了开放封闭原则。我们又一次看到,这些设计原则通常是结合在一起使用的。
通过强制让 Authenticator
依赖着一个存储抽象层,我们就可以使用任何实现了UserProviderInterface
接口的存储系统,且不用对 Authenticator
本身做任何修改。传统的依赖关系已经被反转了,代码变得更灵活、易变、优雅。