怎樣理解依賴反轉(zhuǎn)

依賴反轉(zhuǎn)的定義

在面向?qū)ο箝_發(fā)中有一個(gè)基本的設(shè)計(jì)原則叫依賴反轉(zhuǎn)/倒置(dependency inversion)。維基百科對(duì)于它的定義如下:

  1. 高層次的模塊不應(yīng)該依賴于低層次的模塊,兩者都應(yīng)該依賴于抽象接口。
  2. 抽象接口不應(yīng)該依賴于具體實(shí)現(xiàn)。而具體實(shí)現(xiàn)則應(yīng)該依賴于抽象接口。

剖析

但是當(dāng)我看到這樣的定義時(shí),我是十分困惑的。什么是依賴?什么是反轉(zhuǎn)?為什么定義了抽象接口,作為中間層會(huì)被稱為依賴反轉(zhuǎn)?如果你和我有相同的疑問(wèn),不妨跟我一起看下去。

我們先看一下下面這段傳統(tǒng)的示例代碼片段 1:

class B {
  void mb(){}
}

class A {
  public void cb() {
      (new B()).mb();
  }
}

上面的代碼非常簡(jiǎn)單,在 Acb 方法創(chuàng)建了 B 的對(duì)象并調(diào)用了它的 mb 方法。 在這里 A 需要 B 才能實(shí)現(xiàn) cb 的功能,所以我們稱 A 依賴于 B。 這里我們同時(shí)需要知道的是 A 是 “高層次”, “B” 屬于低層次。

按照‘‘依賴反轉(zhuǎn)’’的定義, 高層次的模塊不應(yīng)該依賴于低層次的模塊,兩者都應(yīng)該依賴于抽象接口, 所以 “B” 是要抽象出來(lái)的。我們把上面的代碼改造成下面的代碼片段 2:

interface IB {
  void mb();
}

class B implements IB {
  void mb(){}
}

class A {
  public void cb(IB b) {
      b.mb();
  }
}

這樣A 不再依賴于具體的 B 的對(duì)象,而是依賴于 IB 接口。B 也實(shí)現(xiàn)了 IB 接口。可以說(shuō)這里我們已經(jīng)就滿足了 “依賴反轉(zhuǎn)” 的定義:

  1. A 不依賴于 B,AB 都依賴于抽象接口 IB。
  2. IB 不依賴于具體實(shí)現(xiàn) A、B。而具體實(shí)現(xiàn) B、A 則應(yīng)該依賴于抽象接口 IB。

總結(jié)下,現(xiàn)在依賴關(guān)系的變化:A -> B 變成了 A -> IB, B -> IB

問(wèn)題來(lái)了,“反轉(zhuǎn)” 從何而來(lái)?不妨繼續(xù)看看下面的代碼。

void main() {
  (new A()).cb();
}

在這段代碼中,我們?cè)?main 函數(shù),調(diào)用了 代碼片段 1A.cb 方法。不難理解,在這里,main 函數(shù)其實(shí)又是 A 的高層次。我們實(shí)現(xiàn)的是高層次依次調(diào)用低層次的具體實(shí)現(xiàn), main -> new A() -> new B()。接著我們看看 main 調(diào)用 代碼片段2 會(huì)有什么不同?

void main() {
  IB b = new B();
  (new A(b)).cb();
}

寫到這里,我忽然覺(jué)得不需要解釋什么了,代碼已經(jīng)說(shuō)明了一切。對(duì)于高層次的 A 來(lái)講,低層次的 B 卻被先創(chuàng)建了出來(lái),變成了 main -> new B() -> new A()。這就是 “反轉(zhuǎn)” 的意思。低層次的實(shí)現(xiàn)被推遲到了更高的層次。

總結(jié),依賴反轉(zhuǎn)是從調(diào)用者的視角看的。傳統(tǒng)的代碼實(shí)現(xiàn),是高層次調(diào)用低層次的具體實(shí)現(xiàn),但是使用了依賴反轉(zhuǎn)后,低層次的具體實(shí)現(xiàn),被提升到了更高的層次。

擴(kuò)展

依賴反轉(zhuǎn)的優(yōu)點(diǎn):

上面我們結(jié)合概念和代碼,解釋了什么是依賴反轉(zhuǎn)。那么依賴反轉(zhuǎn)的優(yōu)點(diǎn)是什么?

  1. 解耦合:A 不再?gòu)?qiáng)綁定于 B, 任何實(shí)現(xiàn)了 IB 接口的類,都能被傳入 A 中;
  2. 擴(kuò)展性:任何實(shí)現(xiàn)了 IB 接口的類,都能被傳入到 A 中,而無(wú)需修改 A 的代碼;

其他 “依賴” 的表現(xiàn)形式

class A  {
   C mc;
   public A(C c) {
    mc = c; 
  }
  public setD(D d) {
    ....
  }
}

在上面的構(gòu)造函數(shù), setD 都屬于依賴。想必會(huì)加深對(duì)依賴的理解。

依賴反轉(zhuǎn)和依賴注入是什么關(guān)系

依賴注入是依賴反轉(zhuǎn)的一種具體的實(shí)現(xiàn)方式。

自問(wèn)自答

自問(wèn): 如果沒(méi)有創(chuàng)建 IB 接口,通過(guò)下面的方式實(shí)現(xiàn),是不是也是依賴反轉(zhuǎn)?

class A {
  public void cb(B b) {
      b.mb();
  }
}

void main() {
    B b = new B();
    (new A(b)).cb();
}

自答: 首先毫無(wú)疑問(wèn)這是依賴注入,依賴注入是依賴反轉(zhuǎn)的一種具體實(shí)現(xiàn),所以是。在 dart 語(yǔ)言中,類本身也是接口,從這種層面上來(lái)講,完全是 ok 的。但是從 java 來(lái)講又不完全是,因?yàn)椴环弦蕾嚪崔D(zhuǎn)的具體定義,只能傳 B 的對(duì)象, 這就破壞了代碼的靈活性、擴(kuò)展性、非耦合性,就算它是,也是一種很爛的實(shí)現(xiàn)。

所以我們應(yīng)該嚴(yán)格按照依賴反轉(zhuǎn)的定義去編寫我們具體的代碼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容