天天看點

3.依賴倒置原則(Dependence Inversion Principle)

1.定義

      高層子產品不應該依賴于低層子產品,二者都應該依賴于抽象;抽象不應該依賴細節;細節應該依賴抽象。

2.定義解讀

     依賴倒置原則在程式編碼中經常運用,其核心思想就是面向接口程式設計,高層子產品不應該依賴低層子產品(原子操作的子產品),兩者都應該依賴于抽象。我們平時常說的“針對接口程式設計,不要針對實作程式設計”就是依賴倒轉原則的最好展現:接口(也可以是抽象類)就是一種抽象,隻要不修改接口聲明,大家可以放心大膽調用,至于接口的内部實作則無需關心,可以随便重構。這裡,接口就是抽象,而接口的實作就是細節。

     如果不管高層子產品還是底層子產品,它們都依賴于抽象,具體一點就是接口或者抽象類,隻要接口是穩定的,那麼任何一個的更改都不用擔心其他受到影響,這就使得無論高層子產品還是低層子產品都可以很容易地被複用。

     依賴倒轉原則其實可以說是面向對象設計的标志,用哪種語言來編寫程式不重要,如果編寫時考慮的都是如何針對抽象程式設計而不是針對細節程式設計,即程式中所有的依賴關系都是終止于抽象類或者接口,那就是面向對象的設計,反之那就是過程化的設計(說這句話可能不怎麼好了解,再加上一句話就好了解了:面向對象的設計,出發點就是應對變化的問題)。

     再舉一個生活中的例子,電腦中記憶體或者顯示卡插槽,其實是一種接口,而這就是抽象;隻要符合這個接口的要求,無論是用金士頓的記憶體,還是其它的記憶體,無論是4G的,還是8G的,都可以很友善、輕松的插到電腦上使用。而這些記憶體條就是具體實作,就是細節。

     【錯誤做法】:抽象A依賴于實作細節b

3.依賴倒置原則(Dependence Inversion Principle)

     【正确做法】:抽象A依賴于抽象B,實作細節b實作抽象B

3.依賴倒置原則(Dependence Inversion Principle)

3.優點

  • 代碼結構清晰,維護容易。

4.問題提出

     類A直接依賴類B,假如需要将類A改為依賴類C,則必須通過修改類A的代碼來達成。這種場景下,類A一般是高層子產品,負責複雜的業務邏輯;類B和類C是低層子產品,負責基本的原子操作;假如修改類A,會給程式帶來不必要的風險。

5.解決方案

     将類A修改為依賴接口I,類B和類C各自實作接口I,類A通過接口I間接與類B或者類C發生聯系,則會大大降低修改類A的幾率。

     依賴倒置原則基于這樣一個事實:相對于細節的多變性,抽象的東西要穩定的多。以抽象為基礎搭建起來的架構比以細節為基礎搭建起來的架構要穩定的多。在C#/Java中,抽象指的是接口或者抽象類;在Objective-C中,抽象指的是委托/協定,細節就是具體的實作類,使用接口或者抽象類的目的是制定好規範和契約,而不去涉及任何具體的操作,把展現細節的任務交給它們的實作類去完成。

6.示例

     繼續發工資的場景:這裡,類SalaryManage(類似上面說的類A)負責工資的管理;Director(類似上面說的類B)是總監類,現在我們要通過SalaryManage類來給總監發放工資了,主要代碼片段如下所示:

class Director
{
    var name: String;
    
    init(name: String)
    {
        self.name = name;
    }
    
    func calculateSalary()
    {
        print("\(name)總監的工資是10000");
    }
}

class SalaryManage
{
    func calculateSalary(director: Director)
    {
        director.calculateSalary();
    }
}

//調用
let salaryManage = SalaryManage();
salaryManage.calculateSalary(Director(name: "張三"));  //列印:張三總監的工資是10000      

     這樣給總監發放工資的功能已經很好的實作了,現在假設需要給經理發工資,我們發現工資管理類SalaryManage沒法直接完成這個功能,需要我們添加新的方法,才能完成。再假設我們還需要給普通員工、财務總監、研發總監等更多的崗位發送工資,那麼我們就隻能不斷的去修改SalaryManage類來滿足業務的需求。産生這種現象的原因就是SalaryManage與Director之間的耦合性太高了,必須降低它們之間的耦合度才行。是以我們引入一個委托EmployeeDelegate,它提供一個發放工資的方法定義,如下所示:

protocol EmployeeDelegate
{
    func calculateSalary();
}

class Director: EmployeeDelegate
{
    var name: String;
    
    init(name: String)
    {
        self.name = name;
    }
    
    func calculateSalary()
    {
        print("\(name)總監的工資是10000");
    }
}

class SalaryManage
{
    func calculateSalary(employee: EmployeeDelegate)
    {
        employee.calculateSalary();
    }
}

//調用
let salaryManage = SalaryManage();
salaryManage.calculateSalary(Director(name: "張三"));  //列印:張三總監的工資是10000      

     這樣修改後,無論以後怎樣擴充其他的崗位,都不需要再修改SalaryManage類了。代表高層子產品的SalaryManage類将負責完成主要的業務邏輯(發工資),如果需要對SalaryManage類進行修改,引入錯誤的風險極大。是以遵循依賴倒置原則可以降低類之間的耦合性,提高系統的穩定性,降低修改程式造成的風險。

     同樣,采用依賴倒置原則給多人并行開發帶來了極大的便利,比如在上面的例子中,剛開始SalaryManage類與Director類直接耦合時,SalaryManage類必須等Director類編碼完成後才可以進行編碼和測試,因為SalaryManage類依賴于Director類。按照依賴倒置原則修改後,則可以同時開工,互不影響,因為SalaryManage與Director類一點關系也沒有,隻依賴于協定(Java和C#中稱為接口)EmployeeDelegate。參與協作開發的人越多、項目越龐大,采用依賴導緻原則的意義就越重大。

無善無惡心之體,

有善有惡意之動,

知善知惡是良知,

為善去惡是格物。