天天看點

面試官今天問我軟體設計的依賴反轉原則,問到槍口了...可他沒想到我都會1 前言2 定義3 傳統分層架構缺陷4 更多案例5 設計原理6 改造案例7 總結

1 前言

當我們使用Spring開發應用時,無需在程式中調用Spring的代碼,就可使用Spring的功能特性。比如依賴注入、MVC,進而開發出高内聚低耦合的應用代碼。

我們自己也寫代碼,能夠做到讓其他工程師不調用我們的代碼就可以使用我們的代碼的功能特性嗎?大多數開發者應該做不到吧!那麼Spring是如何做到的?

2 定義

DIP是指一種特定的解耦(傳統的依賴關系建立在高層次,而具體的政策設定則應用在低層子產品)形式,使得高層子產品不依賴于低層子產品的實作細節,依賴關系被反轉,進而使得低層子產品依賴于高層子產品的需求抽象。

該原則規定:

高層子產品不應該依賴低層子產品,二者都應該依賴抽象接口

抽象接口不應該依賴于具體實作,而具體實作則應該依賴于抽象接口

該原則颠倒了一部分人對OOP的認識方式。如高層、低層對象都應該依賴相同的抽象接口。

正常應用的分層架構,政策層會依賴方法層,業務邏輯層會依賴資料存儲層。這種高層子產品依賴低層子產品的分層架構有什麼缺點呢?

3 傳統分層架構缺陷

3.1 維護困難

高層子產品通常是業務邏輯和政策模型,是一個軟體的核心。正是高層子產品使一個軟體差別于其他軟體,而低層子產品則更多的是技術細節。若高層子產品依賴低層,就是業務邏輯依賴技術細節,技術細節改變将影響業務邏輯。由技術細節改變而影響業務代碼,這顯然不合理。

3.2 複用困難

通常越高層,複用價值越高。但若高層子產品依賴低層子產品,那麼對高層子產品的依賴将會導緻對底層子產品的連帶依賴,複用困難。

4 更多案例

在日常開發中,很多地方都使用了依賴倒置原則。比如通路資料庫,代碼并非直接依賴DB驅動,而是依賴JDBC。各種DB驅動都實作了JDBC,當應用程式需要更換DB時,無需修改任何代碼。這正是因為應用代碼,高層子產品,不依賴DB驅動,而是依賴抽象JDBC,而DB驅動作為低層子產品,也依賴JDBC。

Web應用也無需依賴Tomcat容器,隻需依賴J2EE規範,Web應用實作J2EE規範的Servlet接口,然後把應用程式打包通過Web容器啟動即可處理HTTP請求了。Web容器可以是Tomcat、Jetty,任何實作了J2EE規範的Web容器。

其他MVC架構,ORM架構,也都遵循該原則。

5 設計原理

下面,我們進一步了解下依賴倒置原則的,看看如何在我們的程式設計開發中也能利用依賴倒置原則,開發出更少依賴、更低耦合、更可複用的代碼。

習慣上政策層依賴方法層,方法層依賴工具層。該分層依賴的一個潛在問題是,政策層對方法層和工具層是傳遞依賴,下面兩層的任何改動都會導緻政策層改動,這種傳遞依賴導緻的級聯改動可能會導緻軟體維護過程非常糟糕。

解決辦法是利用依賴反轉,每個高層子產品都為它所需要的服務聲明一個抽象接口,而低層子產品則實作這些抽象接口,高層子產品通過抽象接口使用低層子產品。

面試官今天問我軟體設計的依賴反轉原則,問到槍口了...可他沒想到我都會1 前言2 定義3 傳統分層架構缺陷4 更多案例5 設計原理6 改造案例7 總結

這樣高層無需直接依賴低層子產品,而變成了低層子產品依賴高層子產品定義的抽象接口,進而實作了依賴反轉,解決了傳遞依賴問題。

是以日常開發通常也都依賴抽象接口,而不是依賴具體實作。

那麼Web開發中,Service層依賴DAO層,并非直接依賴DAO的具體實作,而是依賴DAO提供的抽象接口。那麼這種依賴是否是依賴反轉呢?

并不是,依賴反轉原則中,除了具體實作要依賴抽象,最重要的是,抽象是屬于誰的抽象。

通常低層子產品擁有自己的接口,高層子產品依賴低層子產品提供的接口,比如方法層有自己的接口,政策層依賴方法層的接口;DAO層定義自己的接口,Service層依賴DAO層定義的接口。

但是按照依賴反轉原則,接口的所有權被反轉,即接口被高層子產品定義,高層子產品擁有接口,低層子產品實作接口。不是高層子產品依賴底層子產品的接口,而是低層子產品依賴高層子產品的接口,進而實作依賴關系反轉。

在上面的依賴層次中,每層接口都被高層子產品定義,由低層子產品實作,高層子產品完全不依賴低層子產品,即使是低層子產品的接口。這樣,低層子產品的改動不會影響高層子產品,高層子產品的複用也不會依賴低層子產品。對于Service和DAO,就是Service定義接口,DAO實作接口,這樣才符合依賴反轉。

6 改造案例

依賴反轉适于一個類向另一個類發送消息的場景。

6.1 正常的依賴實作設計

Button按鈕控制Lamp燈泡,按鈕按下,燈泡點亮或關閉。

  • 按正常設計,可能會設計如下UML,Button類直接依賴Lamp類。
  • 面試官今天問我軟體設計的依賴反轉原則,問到槍口了...可他沒想到我都會1 前言2 定義3 傳統分層架構缺陷4 更多案例5 設計原理6 改造案例7 總結
public class Button {   
    private Lamp lamp;   
    public void Poll() {     
        if (/* 某條件 */) {      
            lamp.TurnOn();   
        } 
    }
}      

該設計問題是Button依賴Lamp,對Lamp的任何改動,都可能牽扯Button,還無法重用Button類。比如,我們期望通過Button控制一個電機的啟動或者停止,這種設計顯然難以重用Button,因為我們的Button還在依賴Lamp。

應該将該設計中的依賴實作,重構為依賴抽象(這裡就是打開/關閉目标對象)。

6.2 依賴抽象設計

面試官今天問我軟體設計的依賴反轉原則,問到槍口了...可他沒想到我都會1 前言2 定義3 傳統分層架構缺陷4 更多案例5 設計原理6 改造案例7 總結

由Button定義一個抽象接口ButtonServer,其中描述抽象:打開/關閉目标對象。

由具體的目标對象,比如Lamp實作該接口,進而完成Button控制Lamp。

通過依賴反轉,Button不再依賴Lamp,而是依賴抽象ButtonServer,Lamp也依賴ButtonServer,高層子產品和低層子產品都依賴抽象。Lamp改動不影響Button,而Button也可複用以控制其他實作ButtonServer接口的目标對象。

抽象接口ButtonServer的所有權反轉,它不屬底層子產品Lamp,而屬高層子產品Button。

依賴反轉也就是不要來調用我,我會調用你。Tomcat、Spring都是基于該原則,應用程式不需要調用Tomcat或者Spring這樣的架構,而是架構調用應用程式。而實作這一特性的前提就是應用程式必須實作架構的接口規範,比如實作Servlet接口。

7 總結

高層子產品不依賴低層子產品,都依賴抽象接口,該抽象接口通常由高層定義,低層實作。

可得如下程式設計實踐:

多使用抽象接口,避免使用具體的實作類

不要繼承具體類,若一個類在設計之初不是抽象類,就盡量不繼承它。對具體類的繼承是一種強依賴,維護時難以改變

不要重寫包含具體實作的方法

架構的設計。架構提供核心功能,比如HTTP處理,MVC等,并提供一組接口規範,應用程式隻需要遵循接口規範程式設計,就可被架構調用。程式使用架構的功能,但不調用架構的代碼,而是實作架構的接口,被架構調用,是以架構才有如此高可複用性

參考

https://zh.wikipedia.org/wiki/%E4%BE%9D%E8%B5%96%E5%8F%8D%E8%BD%AC%E5%8E%9F%E5%88%99 https://flylib.com/books/en/4.444.1.71/1/