設計原則之【依賴反轉原則】

Gopher大威發表於2022-02-25

表妹讓我幫她修收音機,把我給整懵了...

表妹:哥啊,我的電腦藍屏當機了?

我:我幫你看看。

一頓操作...

我:你這裡有兩個記憶體條,但是其中一根壞了,我現在把它給卸了,暫時用那根好的。

表妹:哇!果然可以了,真​棒?對了,哥啊,我老爸有臺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 {
   @Override
   public void pizza() {
       System.out.println("芝士披薩");
  }
}

public class seafoodPizza implements IPizza {
   @Override
   public void pizza() {
       System.out.println("海鮮披薩");
  }
}

public class superSupreme implements IPizza {
   @Override
   public void pizza() {
       System.out.println("超級至尊");
  }
}

這時上層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

https://www.wmyskxz.com/2019/11/18/tan-yi-tan-yi-lai-dao-zhi-yuan-ze/

相關文章