軟體架構設計原則一切都是為了下面這兩點,別忘了。
這一篇文章我們將要來談談 DIP 依賴反向原則。
根據 wiki 它的定義如下 :
高層次的模組不應該依賴於低層次的模組,兩者都應該依賴於抽象介面。抽象不要依賴細節,細節要依賴與抽象
之前的同事在分享這個主題時要提到個要點,我覺得很讚 ~
那這裡先來問個問題,高層次與低層次是指啥呢 ? 我自已的定義是這樣 :
簡單說個例子,如果以購物車結帳,則我們可以定義成以為兩個高與低模組 :
我知道應該有人會有點 confuse,因為在不少人的專案中根據分層架構,上面的應該都是放在 service 層裡或是直接在 controller 層裡,然後他們會覺在同一層的東西,應該沒有分高、低層次,而只有在 sevice → model 這才是高、低。
我同意我以前也有這樣的想法,但和我接觸到爛 code… 就發覺… 走到那都會碰到 service 層的 cycle dependency,就覺得如果 :
將分層定義為高層次與低層次要有個前提假設,那就是你實作上真的有分層
以我現在大部份的三層架構,大部份的東西都會擠在 service 層,而這時相信我,他真的要分清楚裡面的高層次與低層次… ( 通常這代表可以在拉個層級了 ),不要單純的用分層來判斷,不然你真的會寫到懷疑人生。
這裡簡單給個違反 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)
}
}
首先要先將高層次的 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,這也導致以下兩個問題 :
這裡事實上有幾個知識點可以連結 :
我年輕時是從 php laravel container 那得到的知識,可以參考一下。