依赖倒置就是每一个实现都要抽一个接口出来吗?

本文的标题实际上来自于一次与项目上同事中午吃饭时的讨论:

A: 我觉得我们现在的抽象有点多,infra 层里面每一个类都抽取了接口,这些被调用的类多半只有一个实现, 我们是不是做的太细了?

B: 从依赖倒置的角度讲,domain 层和 service 层并不应该直接调用 infra 层的实现,因此我们确实是需要每一个实现都抽一个接口出来。

A: 那依赖倒置就是每一个实现都要抽一个接口出来吗?

B: 这个...

看来小伙伴 A 不经意间触碰到了 S.O.L.I.D. 的深水区...

相比于单一职责、开闭、接口隔离等原则,依赖倒置与里氏替换类似,属于更偏向操作指导的一类原则,比如从依赖倒置的定义来看:

依赖倒置:高层模块不应直接依赖低层模块,他们都应该依赖于彼此间的抽象。

以开发的角度理解:高层不要直接调用低层,而是调用抽取出来的接口。

那这么说,依赖倒置就是每一个实现都要抽一个接口出来吗?

为了解释这个问题,我们尝试来提出一个新的问题:为啥要依赖倒置?

为啥要依赖倒置

先说结论:因为依赖倒置能隔离变化,使核心业务更稳定。

代码是由业务需求驱动出来的,而业务驱动路径一定是从高层(较为稳定的核心业务层)逐渐传递至低层(较为多变的外围支撑层)。高层不会去了解低层的实现细节,而只会对低层给出需求的定义。依赖倒置就是要明确需求的定义。

我们引入一个例子,

业务需求如下:

对于某文档管理系统,业务上需要对用户创建的文档进行存取。现有若干 Document 文档对象,期望提供某种服务,对文档进行存储,要求存储成功后拿到一个该文档的唯一标识 DocId,并且可以通过该 DocId 再次取回该文档对象。

显然,该系统核心业务是对文档的管理与操作。而将文档存储至某种库,之后对其建立索引并关联唯一标识的工作,应该属于对核心业务的一种支撑。所以,将之设计为独立的低层模块比较合适。而高层模块只需要知道我能提供什么,以及我能得到什么即可。所以,高层业务可以抽象出如下 API 来描述这一需求:

id:DocId saveDocument(doc:Document)

doc:Document getDocument(id:DocId)

那么假如从低层即服务提供者角度来看呢?

作为服务提供者,也就是需求实现方,我最先想到的也许是:文档对象应该就是文件吧?如果要存储某个文件,存储在独立的文件服务器上会比较稳妥。所以,首先我需要知道文件所在主机的 IP(如有必要还需要相关认证信息),以及文件的绝对路径。之后的实现过程可以分为以下几步:

  1. 登录到主机上,根据路径找到文件;
  2. 远程复制该文件到独立的文件服务器;
  3. 生成一个唯一标识,并与文件服务器的真实路径关联;
  4. 将唯一标识以 String 的形式返回给调用者。

因此对于服务提供方,可能会提供如下 API:

id:String saveFile(ip:String, path:Path)

void getFile(id:String, ip:String, path:Path)

显然,提供方和消费方给出的 API 大相径庭。服务提供方甚至根本就默认把 ”文档“ 这样一个业务概念脑补成了 ”文件“ 。

说了半天,没提依赖倒置呀?

我们顺着上文的思路,来想一想,假如高低层模块都是由同一个团队来开发维护,并且按照业务驱动的模式来开发,上述需求会怎么一步步变成代码呢?

  1. 出现业务需求,期望对文档进行存取
  2. 团队认为,具体文档存取的实现应该不属于 domain 层,而是 infrastructure 层。
  3. 为了不影响业务卡的开发,团队根据讨论,提取出了文档存取所需的抽象,即:
     id:DocId saveDocument(doc:Document)
    
     doc:Document getDocument(id:DocId)
    
  4. 某小伙伴领取实现文档存取的故事卡,先通过工具类获取本机 IP,之后从文档对象中拿到实际的 file,以及对应的元数据,之后存储至远端文件服务,元数据入库,返回唯一 id。
  5. 后来,由于部署环境变更,远程文件服务不可用,文件存储要改为存储在本地,对于这个需求,做卡的小伙伴只要遵循抽象,重新实现一套本地存储的方案即可,对高层业务完全透明。

可见,由于对需求进行了明确的定义,产出了需求的稳定抽象,基于此抽象的实现,不论如何变化,都不会影响到核心业务的稳定,这就是依赖倒置。由业务需求驱动的开发天然满足了依赖倒置的要求,层与层之间互相解耦,整个系统也就对变化表现出了更强的适应性。

不过实际当中,很多时候不同模块的开发是由不同团队完成的,我们也许没办法左右已经提供了 API 的基础设施,这时怎么办呢?

在实践 DDD 中,我们经常会听到六边形架构的概念,六边形架构内所有的业务逻辑与其他外部依赖之间,全部采用适配器(Adapter)进行适配,以尽可能的隔离业务边界,增加扩展性。

所以引申到上述例子,假如系统现有的文件服务提供给我们的 API 必须要以 IP 和文件路径作为参数,那么为了防止业务与外部服务产生依赖,我们仍旧以业务需求驱动的方式,提取文档抽象,之后新增适配器,适配器一端依赖抽象,另一端依赖外部文件服务。通过这种办法就可以很好的实现依赖倒置。

有了适配器,无论外部服务怎么变化,只要跟着改适配器,我们的业务仍然是高度内聚的。

回过头来

前文聊了聊为什么要依赖倒置以及怎么进行依赖倒置。现在,我们可以再回到最开始的问题本身:

依赖倒置就是每一个实现都要抽一个接口出来吗?

答案显而易见了:
恰恰相反,依赖倒置应该是先由业务消费方定义接口,再由服务提供方实现,只不过从最终产出物的角度看,的确是可能每个实现都抽取了一个接口而已。

因此假如作为服务提供方,为了满足依赖倒置,臆想消费方的需求来抽取接口,那不叫依赖倒置,叫本末倒置。

最后总结一下

  1. 什么是依赖倒置: 高层模块不应直接依赖低层模块,他们应该都依赖于彼此间的抽象。
  2. 为什么要依赖倒置: 因为依赖倒置能隔离变化,使核心业务更稳定。
  3. 怎么实现依赖倒置: 核心业务方定义需求抽象,服务提供方实现需求抽象。

原创文章,作者 LENSHOOD, 首发自:https://lenshood.github.io/2020/01/25/is-dependency-invertion-require-every-class-extract-a-interface/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352

推荐阅读更多精彩内容