From Apprentice To Artisan 翻译 20

Stella981
• 阅读 477

上一篇

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 UserProviderInterface contract. The Authenticator is no longer depending directly on low-level storage operations.

做了这些小改动后,Authenticator现在依赖于两个高级抽象:UserProviderInterfaceHasherInterface。我们可以向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本身做任何修改。传统的依赖关系链已经被反转了,代码变得更灵活,更加无惧变化!


回到目录

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这