文章目錄
- 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 設計模式》