聊聊設計模式原則(三) -- 依賴倒置原則

目錄

依賴倒置原則(DIP :Dependence Inversion Principle)

定義

  • 高層模塊不應該依賴低層模塊,二者都應該依賴其抽象;
  • 抽象不應該依賴細節(jié);
  • 細節(jié)應該依賴抽象。
也就是說高層模塊,低層模塊,細節(jié)都應該依賴抽象

由來

類A直接依賴類B,假如要將類B改為類C,則必須通過修改類A的代碼來達成。
類A一般是高層模塊,負責復雜的業(yè)務邏輯。
類B和類C是低層模塊,負責基本的原子操作。
修改類A,會給程序帶來不必要的風險。

由來

解決方案

將類A修改為依賴接口I,類B和類C各自實現(xiàn)接口I,類A通過接口I間接與類B或者類C發(fā)生聯(lián)系,則會大大降低修改類A的幾率。

解決方案

優(yōu)點

依賴倒置原則可以減少類間的耦合性,提高系統(tǒng)的穩(wěn)定,降低并行開發(fā)引起的風險,提高代碼的可讀性和可維護性。

思考

1.依賴倒置原則跟面向接口編程是什么關系?

依賴倒置原則的核心思想就是面向接口編程

2.什么是細節(jié)?什么是抽象?他們有什么區(qū)別?

所謂細節(jié)就是較為具體的東西,比如具體的類,就如上面的類B與類C,有具體的實現(xiàn)。

所謂抽象就是具有契約性、共同性、規(guī)范性的表達,比如上面的接口I。它表達了一種契約--你需要實現(xiàn)funcA和funcB才能被當成I來對待。

相對于細節(jié)的多變性,抽象的東西要穩(wěn)定的多。
以上面的類ABC作為例子,B、C類都屬于細節(jié),如果A直接依賴B或者C,那么B或C的改動有可能就會影響到A的穩(wěn)定性。同樣的,A對B或者C的操作也有可能影響到B或C的穩(wěn)定性。這些互相影響,其實來源于直接的依賴,導致B或C的細節(jié)暴露過多。而面對抽象的接口I,A只能操作funA和funcB,從而避免了不必要的暴露和風險。

以抽象為基礎搭建起來的架構(gòu)比以細節(jié)為基礎搭建起來的架構(gòu)要穩(wěn)定的多。
穩(wěn)定性表現(xiàn)在規(guī)范性、契約性、易修改性、擴展性、可維護性等。

來個栗子

小明(程序員)接到小李(產(chǎn)品經(jīng)理)的需求:
客戶端開始的時候打個“開始”的log。
小明心想簡單,一下子完成了代碼

public class Logger {
    func log(_ text: String) {
        print(text)
    }
}

public class Client {
    public var logger: Logger?

    func start() {
        logger?.log("開始")
    }
}

let client = Client()
let logger = Logger()
client.logger = logger

client.start()

這個時候,小李突然說,要把log變成存到文件的形式。
小明想了下,有點不情愿地改了代碼(因為要改好幾個地方)

public class FileLogger { //修改1
    func log(_ text: String) {
        writeTofile(text) //修改2
    }
    
    func start() {
        print("開始")
    }
}

public class Client {
    public var logger: FileLogger? //修改3

    func start() {
        logger.start()
        logger?.log("開始")
    }
}

let client = Client()
let logger = FileLogger() //修改4
client.logger = logger

client.start()

小李想了想現(xiàn)在是互聯(lián)網(wǎng)時代,還是直接將log信息傳到網(wǎng)絡上吧。
這個時候,小明非常不情愿地說了聲“你不早說”,但還是改了代碼(又是好幾處改動)

public class WebLogger { //修改1
    func log(_ text: String) {
        writeToWeb(text) //修改2
    }
}

public class Client {
    public var logger: WebLogger? //修改3

    func start() {
        logger?.log("開始")
    }
}

let client = Client()
let logger = WebLogger() //修改4
client.logger = logger

client.start()

這時,小明的老大小華看到小明不開心,便過來幫忙,改了下代碼

protocol Logger {
    func log(_ text: String)
}

public class WebLogger: Logger {
    func log(_ text: String) {
        writeToWeb(text) 
    }
}

public class FileLogger: Logger {
    func log(_ text: String) {
        writeTofile(text) 
    }
}

public class PrintLogger: Logger {
    func log(_ text: String) {
        print(text)
    }
}

public class Client {
    public var logger: Logger? 

    func start() {
        logger?.log("開始")
    }
}

let client = Client()
let logger = WebLogger()
client.logger = logger

client.start()

小華對小明說,現(xiàn)在不用怕了,小李想什么樣的log你改一下實現(xiàn)類就行了

let logger = WebLogger() // PrintLogger()  FileLogger()

小華的改動其實就是利用了依賴倒置原則,增強了易修改性、擴展性、可維護性等。
細心的朋友其實還發(fā)現(xiàn)了,在改成FileLogger的時候,Client多余地調(diào)用了FileLogger的start方法。這就是依賴細節(jié),暴露細節(jié),引起的問題。而使用抽象的接口就能較好地避免這類問題。

注意點

  • 分清細節(jié)與抽象
    雖然依賴倒置原則有很大的好處,但也不是所有的類都需要有抽象一個接口去對應,要視情況而定。

  • 變量的聲明類型盡量是抽象類或接口
    注意是盡量,而不是全部。

  • 盡量不要覆寫基類的方法
    如果基類是一個抽象類,而這個方法已經(jīng)實現(xiàn)了,子類盡量不要覆寫。類間依賴的是抽象,覆寫了抽象方法,對依賴的穩(wěn)定性會有一定的影響。

  • 繼承要遵循里氏替換原則

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容