30-6 之软件架构设计原则 5 - DIP 依赖反向原则

软件架构设计原则一切都是为了下面这两点,别忘了。

  • 低耦合
  • 高内聚

这一篇文章我们将要来谈谈 DIP 依赖反向原则。

DIP ( Dependency -Inversion Principle ) 依赖反向原则

根据 wiki 它的定义如下 :

高层次的模组不应该依赖於低层次的模组,两者都应该依赖於抽象介面。抽象不要依赖细节,细节要依赖与抽象

之前的同事在分享这个主题时要提到个要点,我觉得很赞 ~

  • Bad - 用 iphone 和家人说话
  • Good - 用手机和家人说话

那这里先来问个问题,高层次与低层次是指啥呢 ? 我自已的定义是这样 :

  • 高层次模组 : 要完成的商业目标所建立的模组
  • 低层次模组 : 要完成高层次模组,所需完成的任务,例如存存 db 啊或发信通知啊啥的

简单说个例子,如果以购物车结帐,则我们可以定义成以为两个高与低模组 :

  • 高层次模组 : CartCheckout 购物车结帐
  • 低层次模组 : 清空购物车、计算购物车内金额、付款、交付商品、发结帐完成通知信

我知道应该有人会有点 confuse,因为在不少人的专案中根据分层架构,上面的应该都是放在 service 层里或是直接在 controller 层里,然後他们会觉在同一层的东西,应该没有分高、低层次,而只有在 sevice → model 这才是高、低。

我同意我以前也有这样的想法,但和我接触到烂 code… 就发觉… 走到那都会碰到 service 层的 cycle dependency,就觉得如果 :

将分层定义为高层次与低层次要有个前提假设,那就是你实作上真的有分层

以我现在大部份的三层架构,大部份的东西都会挤在 service 层,而这时相信我,他真的要分清楚里面的高层次与低层次… ( 通常这代表可以在拉个层级了 ),不要单纯的用分层来判断,不然你真的会写到怀疑人生。

范例

Bad

这里简单给个违反 DIP 的范例,假设我们有个处理订单结帐的模组,然後他里面要使用 atm 来付款,那这里的问题在於 ~ 如果今天要新增信用卡付款,那这里就会需要修改,事实上这里也违反了 OCP 开放封闭原则。

而这里 DIP 违反了『 高层次 ( OrderCheckout ) 依赖低层次 ( AtmPayment ) 』,这就会导致,如果不在提供 atm 或是新增信用卡付款,这里都需要大改。

// Bad 
class OrderCheckoutService{
    order: any
    constructor(order: any){
        this.order = order
    }

    execute(){
        const payment = new AtmPayment()  <-----!!! 这个依赖了 AtmPayment
        payemnt.execute(this.order)
    }
}

Good

首先要先将高层次的 OrderCheckout 所依赖的抽象个 interface 出来,并且低层次的 AtmPayment 也依赖这个 interface。

interface IPayment{
    execute(order: any): void
}

class ATMPayment implements IPayment{
    execute(order: any){
        console.log('ATM pay')
    }
}

然後在将高层次的 OrderCheckout 修改成如下,这样就算这个订单之後要改用 line pay 或啥的,就只要新增一个低层次的付款模组,然後在依赖 IPayment 并实作,高层次模式在指定使用就好了。

class OrderCheckoutService{
    order: any
    constructor(order: any){
        this.order = order
    }

    execute(payment: IPayment){
        payment.execute(this.order)
    }
}

const orderCheckoutService = new OrderCheckoutService('order')
orderCheckoutService.execute(new ATMPayment())

小总结

老样子,来问一下三个问题,来加深记忆

这个知识点可以用来解释什麽现象

这个原则基本上帮我们解释了,『 高层次模组与低层次模组要如何解耦 』,这也代表它是往软件设计原则中的『 低耦合 』前往。

像咱们公司的 service 层基本上就没有符合 DIP,这也导致以下两个问题 :

  • 循环依赖。虽然不能说因为不符合 DIP 所以导致它,但如果有符合基本上就不太可能发生这件事。
  • 替换低层次模组,写了一堆 if else。

这个知识点可以和以前的什麽知识连结呢 ?

这里事实上有几个知识点可以连结 :

  • 依赖注入( DI ) : 基本上它就一种实现 DIP 的方法,可以想成将依赖的物件,改成用 class construct 或 function 的参数代入。
  • DI Container : 简单点,如果每一个方法与类别我们外面使用时,都要指定它是要用什麽,如果一个还好,如果一大堆是不是会很烦,所以这时可以想成,在 app 一开始时,会建立一个 container 容器,然後注册相对应的类别,然後会自动帮你实体化 construct 地方的物件。
  • IOC ( 控制反转 ) : 我觉得它几乎可以算是 DIP 的相同概念。DIP 是一种原则,IOC 可以说实现这原则更具体一点的概念,而 DI 就等同於实作的方法。

我年轻时是从 php laravel container 那得到的知识,可以参考一下。

PHP Laravel Container

我要如何运用这个知识点 ?

  • 在设计时尽量可能分的出高层次与低层次的模组,就算是在同一层级。
  • 高层次不要直接依赖低层次,而是依赖 Interface。
  • 尽量使用 DI,但是要用 construct 或 function 的注入就要想一下,方向可以往,是不是只有这个 function 是特例需要,是的话就 function 参数注入。

参考


<<:  类比数位转换模组

>>:  TailwindCSS 从零开始 - 增加 Base 样式

Day21 vue.js网站删除特定文章

延续昨日 今天来完善我们的 我的专案的功能吧! 首先这是目前的我的专案 再来新增2个按钮 嘟嘟噜新增...

ISO 27001 资讯安全管理系统 【解析】(二十二)

识别威胁 在前面的概论中,我们知道威胁是外来的,他必须配合资产才会产生风险,所以资产与威胁是相互之...

DAY20 MongoDB Oplog 玩坏它

DAY20 MongoDB Oplog 玩坏它 把手弄脏,亲眼於本机见证节点同步跟不上 本篇的目的就...

废文好多,来搞个粉丝专页吧!

今天要分享的故事是关於我在今年初开始经营粉丝专页的收获! 进入正题 虽然现在的年轻人更偏好使用 In...

Day09,我也好想用用看Terraform

正文 今天要来开机器,先去下载server版的Ubuntu 20.04 iso,因为以前自己做环境练...