天天看點

「設計模式」六大原則之五:依賴倒置原則小結

文章目錄

  • ​​1.依賴倒置原則(DIP)定義​​
  • ​​4.DIP舉例說明​​
  • ​​2. 如何了解控制反轉(IOC)​​
  • ​​3. 如何了解依賴注入(DI)​​
  • ​​4. 小結​​

「設計模式」六大原則系列連結:

​​「設計模式」六大原則之一:單一職責小結​​

​​「設計模式」六大原則之二:開閉職責小結​​

​​「設計模式」六大原則之三:裡氏替換原則小結​​

​​「設計模式」六大原則之四:接口隔離原則小結​​

​​「設計模式」六大原則之五:依賴倒置原則小結​​

​​「設計模式」六大原則之六:最小知識原則小結 ​​

六大原則展現很多程式設計的底層邏輯:高内聚、低耦合、面向對象程式設計、面向接口程式設計、面向抽象程式設計,最終實作可讀、可複用、可維護性。

設計模式的六大原則有:

  • Single Responsibility Principle:單一職責原則
  • Open Closed Principle:開閉原則
  • Liskov Substitution Principle:裡氏替換原則
  • Law of Demeter:迪米特法則(最少知道原則)
  • Interface Segregation Principle:接口隔離原則
  • Dependence Inversion Principle:依賴倒置原則

    把這六個原則的首字母聯合起來( L 算做一個)就是 SOLID (solid,穩定的),其代表的含義就是這六個原則結合使用的好處:建立穩定、靈活、健壯的設計。

本文介紹 SOLID 中的第五個原則:依賴倒置原則。

1.依賴倒置原則(DIP)定義

依賴倒置原則的英文翻譯是 Dependency Inversion Principle,縮寫為 DIP。

中文翻譯有時候也叫依賴反轉原則。

為了追本溯源,我先給出這條原則最原汁原味的英文描述:

High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions.

我們将它翻譯成中文,大概意思就是:

  • 高層子產品(high-level modules)不要依賴低層子產品(low-level)。
  • 高層子產品和低層子產品應該通過抽象(abstractions)來互相依賴。
  • 除此之外,抽象(abstractions)不要依賴具體實作細節(details),具體實作細節(details)依賴抽象(abstractions)。

所謂高層子產品和低層子產品的劃分,簡單來說就是,在調用鍊上,調用者屬于高層,被調用者屬于低層。

在 Java 語言中,抽象指的是接口或抽象類,兩都都是不能直接執行個體被化的,細節就是實作類,實作接口或繼承抽象類而産生的類就是細節。其特點就是可以被直接執行個體化,也就是可以加上 new 關鍵字。

高層子產品就是調用端,低層子產品就是具體實作類。

依賴倒置原則在 Java中的展現就是:

子產品間的依賴通過抽象發生,實作類之間不發生直接的依賴關系,其依賴關系通過接口或抽象産生的。

簡單點了解,我們要面向接口程式設計,或者是面向抽象程式設計。

4.DIP舉例說明

在項目中如何靈活的更換第三方圖檔加載庫,或者同時共存,使用者根據手機選擇性使用三方庫。

第一個版本,隻使用一種 Picasso 加載庫

private void loadImage(String url, ImageView imageView) {
    Picasso.get().load(url).resize(50, 50).centerCrop().into(imageView)
}      

第二個版本圖檔加載庫,需要變化, Picasso 換成 Glide, 隻需要修改這一個工具類即可

public class PicassoUtils {
    //從網絡加載
    public static void loadByUrl(String url, IamgeView imageView){
        Picasso.get().load(url).resize(50, 50).centerCrop().into(imageView)
    }

    //從檔案加載
    public static void loadByPath(String path,ImageView imageView) {

    }
}      

第三個版本,需求變化,要求兩種加載庫都需要,根據不同手機适配。(類似的:根據不同的手機選擇不同的推送服務,友善提高消息的到達率)

此時就需要應用好面向接口程式設計思想了。

interface ImageLoader {
    //從網絡加載
    void loadByUrl(String url, ImageView imageView);
    //從檔案加載
    void loadByPath(String path, ImageView imageView);
}

//使用 Picasso
public class PicassoLoader implements ImageLoader {
    ...
}

//使用 Glide
public class GlideLoader implements ImageLoader {
    ...
}

//業務層使用,imageLoader是什麼就看需求,随便建立
//也可以定義一個全局管理類,裡面視需求自定義全局 PicassoLoader 或 GlideLoader
// 調用方法
ImageLoader imageLoader = new PicassoLoader() 
  //ImageLoader imageLoader = new GlideLoader()
imageLoader.loadByUrl(url, imageView);      

可以看到面向接口的好處: 能夠減少耦合,代碼容易維護,容易拓展,因為接口是抽象的,是以不用糾結于具體的邏輯,想怎麼實作都可以;又因為接口是頂層的,是以下層就多,就更容易擴充,就更靈活。

2. 如何了解控制反轉(IOC)

控制反轉的英文翻譯是 Inversion Of Control,縮寫為 IOC。

控制反轉是一個比較籠統的設計思想,并不是一種具體的實作方法,一般用來指導架構層面的設計。這裡所說的“控制”指的是對程式執行流程的控制,而“反轉”指的是在沒有使用架構之前,程式員自己控制整個程式的執行。在使用架構之後,整個程式的執行流程通過架構來控制。流程的控制權從程式員“反轉”給了架構。

3. 如何了解依賴注入(DI)

依賴注入跟控制反轉恰恰相反,它是一種具體的編碼技巧。

依賴注入的英文翻譯是 Dependency Injection,縮寫為 DI。

那到底什麼是依賴注入呢?

我們用一句話來概括就是:不通過 new() 的方式在類内部建立依賴類對象,而是将依賴的類對象在外部建立好之後,通過構造函數、函數參數等方式傳遞(或注入)給類使用。

在這個例子中,Notification 類負責消息推送,依賴 MessageSender 類實作推送商品促銷、驗證碼等消息給使用者。我們分别用依賴注入和非依賴注入兩種方式來實作一下。具體的實作代碼如下所示:

// 非依賴注入實作方式
public class Notification {
  private MessageSender messageSender;
  
  public Notification() {
    this.messageSender = new MessageSender(); //此處有點像hardcode
  }
  
  public void sendMessage(String cellphone, String message) {
    //...省略校驗邏輯等...
    this.messageSender.send(cellphone, message);
  }
}

public class MessageSender {
  public void send(String cellphone, String message) {
    //....
  }
}
// 使用Notification
Notification notification = new Notification();

// 依賴注入的實作方式
public class Notification {
  private MessageSender messageSender;
  
  // 通過構造函數将messageSender傳遞進來
  public Notification(MessageSender messageSender) {
    this.messageSender = messageSender;
  }
  
  public void sendMessage(String cellphone, String message) {
    //...省略校驗邏輯等...
    this.messageSender.send(cellphone, message);
  }
}
//使用Notification
MessageSender messageSender = new MessageSender();
Notification notification = new Notification(messageSender);      

通過依賴注入的方式來将依賴的類對象傳遞進來,這樣就提高了代碼的擴充性,我們可以靈活地替換依賴的類。這一點在我們之前講“開閉原則”的時候也提到過。

當然,上面代碼還有繼續優化的空間,我們還可以把 MessageSender 定義成接口,基于接口而非實作程式設計。改造後的代碼如下所示:

public class Notification {
  private MessageSender messageSender;
  
  public Notification(MessageSender messageSender) {
    this.messageSender = messageSender;
  }
  
  public void sendMessage(String cellphone, String message) {
    this.messageSender.send(cellphone, message);
  }
}

public interface MessageSender {
  void send(String cellphone, String message);
}

// 短信發送類
public class SmsSender implements MessageSender {
  @Override
  public void send(String cellphone, String message) {
    //....
  }
}

// 站内信發送類
public class InboxSender implements MessageSender {
  @Override
  public void send(String cellphone, String message) {
    //....
  }
}

//使用Notification
MessageSender messageSender = new SmsSender();
Notification notification = new Notification(messageSender);      

上面例子是依賴注入的典型用法。

4. 小結

“基于接口而非實作程式設計”與“依賴注入”的聯系是二者都是從外部傳入依賴對象而不是在内部去new一個出來。

差別是“基于接口而非實作程式設計”強調的是“接口”,強調依賴的對象是接口,而不是具體的實作類;

而“依賴注入”不強調這個,類或接口都可以,隻要是從外部傳入不是在内部new出來都可以稱為依賴注入。

參考:

《設計模式之美》

《重學 Java 設計模式》

繼續閱讀