目錄
依賴倒置原則(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)定性會有一定的影響。繼承要遵循里氏替換原則