表妹讓我幫她修收音機,把我給整懵了...
表妹:哥啊,我的電腦藍屏當機了?
我:我幫你看看。
一頓操作...
我:你這裡有兩個記憶體條,但是其中一根壞了,我現在把它給卸了,暫時用那根好的。
表妹:哇!果然可以了,真棒?對了,哥啊,我老爸有臺82年的收音機,壞了好一陣子了,你也幫他修修唄?
我:哈...?這,我就不會啦。
表妹:為啥?電腦都會,小收音機怎麼可能難倒你呢?
我:哈哈,因為,它違背了依賴反轉原則。
表妹:哈?啥意思?
高層模組不要依賴底層模組。高層模組和底層模組應該通過抽象來互相依賴。除此之外,抽象不要依賴具體實現細節,具體實現細節依賴抽象。
如下圖所示:
為什麼收音機很難修呢?
因為各個部件相互依賴,如左圖所示,任何問題都可能涉及其他部件,不懂的人根本沒法修。但是電腦不同,因為無論主機板、CPU、記憶體、硬碟都是針對介面設計的,如右圖所示,那麼,不管哪一個出問題,都可以在不影響別的部件的前提下,進行修改或替換。
比如,現在我們需要實現一個披薩店,該店售有芝士披薩、海鮮披薩、超級至尊等品種。可能我們很容易想到,披薩店是上層模組,具體的披薩品種是下層模組,如果我們把披薩店和披薩品種的關係畫成一張圖,應該是這樣的:
接下來,我們來看一下程式碼實現:
public class PizzaStore {
public void cheesePizza() {
System.out.println("芝士披薩");
}
public void seafoodPizza() {
System.out.println("海鮮披薩");
}
public void superSupreme() {
System.out.println("超級至尊");
}
}
我們來模擬上層呼叫:
public static void main(String[] args) {
PizzaStore pizzaStore = new PizzaStore();
pizzaStore.cheesePizza();
pizzaStore.superSupreme();
pizzaStore.seafoodPizza();
}
如果該店業務的擴充套件,新增很多品種,那麼,這個時候,就需要從底層實現到高層呼叫依次地修改程式碼。
我們需要在PizzaStore類中新增披薩的品種,也需要在高層呼叫中增加呼叫,這樣一來,系統釋出後,其實是非常不穩定的。雖然這個例子比較簡單,修改也不會引入新的bug,但是,實際專案會複雜很多。
最理想的情況是,我們已經編寫好的程式碼可以“萬年不變”,這就意味著已經覆蓋的單元測試可以不用修改,已經存在的行為可以保證保持不變,這就意味著穩定。任何程式碼上的修改帶來的影響都是有未知風險的,不論看上去多麼簡單。
如何降低高低層程式碼之間的耦合?
比如,商品經濟的萌芽時期,出現了物物交換。如果你要買一臺手機,賣手機的人讓你拿一頭豬來換,但是你手裡沒有豬,這時,你就要去找一個賣豬的老闆,但是他要你拿羊來跟他換,你也沒有羊,繼續去找賣羊的人...
你看,這一連串的物件依賴,造成了嚴重的耦合災難。解決這一問題的最好辦法就是,買賣雙方都依賴一個抽象--貨幣,通過貨幣來進行交換,這樣一來耦合度就大為降低了。
我們現在的程式碼是上層直接依賴底層實現,現在我們需要定義一個抽象的IPizza介面,來對這種強依賴進行解耦。如下圖所示:
我們再來看一下程式碼:
先定義一個Pizza的抽象介面IPizza:
public interface IPizza {
void pizza();
}
接下來就是不同的品種的實現類:
public class cheesePizza implements IPizza {
這時上層PizzaStore類依賴IPizza介面即可:
public class PizzaStore {
public void sellPizza(IPizza p) {
p.pizza();
}
}
接下來就是上層呼叫:
public static void main(String[] args) {
PizzaStore pizzaStore = new PizzaStore();
pizzaStore.sellPizza(new cheesePizza());
pizzaStore.sellPizza(new seafoodPizza());
pizzaStore.sellPizza(new superSupreme());
}
你看,在這種設計下,無論該披薩店的品種怎麼擴增,只需新建一個該品種的實現類即可,而不需要修改底層的程式碼。後面,如果該披薩店的業務繼續擴大,除了賣披薩,還賣其他小吃,飲料酒水等,同樣,只需分別抽象出小吃和飲料酒水兩個介面,讓上層呼叫只依賴這些介面即可。
總結
以抽象為基準比以細節為基準搭建起來的架構要穩定得多。
因此,在拿到需求後,要面向介面程式設計,先頂層設計再細節地設計程式碼結構。
好啦,每一種設計原則是否運用得當,應該根據具體業務場景,具體分析。
參考
極客時間專欄《設計模式之美》
《大話設計模式》
https://www.jianshu.com/p/c3ce6762257c