依賴反轉(zhuǎn)的定義
在面向?qū)ο箝_發(fā)中有一個(gè)基本的設(shè)計(jì)原則叫依賴反轉(zhuǎn)/倒置(dependency inversion)。維基百科對(duì)于它的定義如下:
- 高層次的模塊不應(yīng)該依賴于低層次的模塊,兩者都應(yīng)該依賴于抽象接口。
- 抽象接口不應(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)單,在 A 的 cb 方法創(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)” 的定義:
A不依賴于B,A和B都依賴于抽象接口IB。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)用了 代碼片段 1 中 A.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)是什么?
- 解耦合:
A不再?gòu)?qiáng)綁定于B, 任何實(shí)現(xiàn)了IB接口的類,都能被傳入A中; - 擴(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)的定義去編寫我們具體的代碼。