iT邦幫忙

2021 iThome 鐵人賽

DAY 6
0
自我挑戰組

馬克的軟體架構小筆記系列 第 6

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 參數注入。

參考


上一篇
30-5 之軟體架構設計原則 4 - ISP 介面隔離原則
下一篇
30-7 之分層架構 From Patterns of Enterprise Application Architecture
系列文
馬克的軟體架構小筆記29
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言