目錄
依賴倒置原則(DIP :Dependence Inversion Principle)
定義
- 高層模塊不應(yīng)該依賴低層模塊呻袭,二者都應(yīng)該依賴其抽象简僧;
- 抽象不應(yīng)該依賴細(xì)節(jié);
- 細(xì)節(jié)應(yīng)該依賴抽象。
也就是說(shuō)高層模塊,低層模塊,細(xì)節(jié)都應(yīng)該依賴抽象
由來(lái)
類(lèi)A直接依賴類(lèi)B鄙币,假如要將類(lèi)B改為類(lèi)C,則必須通過(guò)修改類(lèi)A的代碼來(lái)達(dá)成蹂随。
類(lèi)A一般是高層模塊爱榔,負(fù)責(zé)復(fù)雜的業(yè)務(wù)邏輯。
類(lèi)B和類(lèi)C是低層模塊糙及,負(fù)責(zé)基本的原子操作详幽。
修改類(lèi)A,會(huì)給程序帶來(lái)不必要的風(fēng)險(xiǎn)浸锨。
解決方案
將類(lèi)A修改為依賴接口I唇聘,類(lèi)B和類(lèi)C各自實(shí)現(xiàn)接口I,類(lèi)A通過(guò)接口I間接與類(lèi)B或者類(lèi)C發(fā)生聯(lián)系柱搜,則會(huì)大大降低修改類(lèi)A的幾率迟郎。
優(yōu)點(diǎn)
依賴倒置原則可以減少類(lèi)間的耦合性,提高系統(tǒng)的穩(wěn)定聪蘸,降低并行開(kāi)發(fā)引起的風(fēng)險(xiǎn)宪肖,提高代碼的可讀性和可維護(hù)性表制。
思考
1.依賴倒置原則跟面向接口編程是什么關(guān)系?
依賴倒置原則的核心思想就是面向接口編程
2.什么是細(xì)節(jié)控乾?什么是抽象么介?他們有什么區(qū)別?
所謂細(xì)節(jié)就是較為具體的東西蜕衡,比如具體的類(lèi)壤短,就如上面的類(lèi)B與類(lèi)C,有具體的實(shí)現(xiàn)慨仿。
所謂抽象就是具有契約性久脯、共同性、規(guī)范性的表達(dá)镰吆,比如上面的接口I帘撰。它表達(dá)了一種契約--你需要實(shí)現(xiàn)funcA和funcB才能被當(dāng)成I來(lái)對(duì)待。
相對(duì)于細(xì)節(jié)的多變性万皿,抽象的東西要穩(wěn)定的多吟策。
以上面的類(lèi)ABC作為例子笆呆,B整慎、C類(lèi)都屬于細(xì)節(jié)款咖,如果A直接依賴B或者C,那么B或C的改動(dòng)有可能就會(huì)影響到A的穩(wěn)定性唤衫。同樣的婆赠,A對(duì)B或者C的操作也有可能影響到B或C的穩(wěn)定性。這些互相影響佳励,其實(shí)來(lái)源于直接的依賴休里,導(dǎo)致B或C的細(xì)節(jié)暴露過(guò)多。而面對(duì)抽象的接口I赃承,A只能操作funA和funcB妙黍,從而避免了不必要的暴露和風(fēng)險(xiǎn)。
以抽象為基礎(chǔ)搭建起來(lái)的架構(gòu)比以細(xì)節(jié)為基礎(chǔ)搭建起來(lái)的架構(gòu)要穩(wěn)定的多瞧剖。
穩(wěn)定性表現(xiàn)在規(guī)范性拭嫁、契約性、易修改性抓于、擴(kuò)展性做粤、可維護(hù)性等。
來(lái)個(gè)栗子
小明(程序員)接到小李(產(chǎn)品經(jīng)理)的需求:
客戶端開(kāi)始的時(shí)候打個(gè)“開(kāi)始”的log捉撮。
小明心想簡(jiǎn)單怕品,一下子完成了代碼
public class Logger {
func log(_ text: String) {
print(text)
}
}
public class Client {
public var logger: Logger?
func start() {
logger?.log("開(kāi)始")
}
}
let client = Client()
let logger = Logger()
client.logger = logger
client.start()
這個(gè)時(shí)候,小李突然說(shuō)巾遭,要把log變成存到文件的形式肉康。
小明想了下闯估,有點(diǎn)不情愿地改了代碼(因?yàn)橐暮脦讉€(gè)地方)
public class FileLogger { //修改1
func log(_ text: String) {
writeTofile(text) //修改2
}
func start() {
print("開(kāi)始")
}
}
public class Client {
public var logger: FileLogger? //修改3
func start() {
logger.start()
logger?.log("開(kāi)始")
}
}
let client = Client()
let logger = FileLogger() //修改4
client.logger = logger
client.start()
小李想了想現(xiàn)在是互聯(lián)網(wǎng)時(shí)代,還是直接將log信息傳到網(wǎng)絡(luò)上吧吼和。
這個(gè)時(shí)候涨薪,小明非常不情愿地說(shuō)了聲“你不早說(shuō)”,但還是改了代碼(又是好幾處改動(dòng))
public class WebLogger { //修改1
func log(_ text: String) {
writeToWeb(text) //修改2
}
}
public class Client {
public var logger: WebLogger? //修改3
func start() {
logger?.log("開(kāi)始")
}
}
let client = Client()
let logger = WebLogger() //修改4
client.logger = logger
client.start()
這時(shí)纹安,小明的老大小華看到小明不開(kāi)心,便過(guò)來(lái)幫忙砂豌,改了下代碼
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("開(kāi)始")
}
}
let client = Client()
let logger = WebLogger()
client.logger = logger
client.start()
小華對(duì)小明說(shuō)厢岂,現(xiàn)在不用怕了,小李想什么樣的log你改一下實(shí)現(xiàn)類(lèi)就行了
let logger = WebLogger() // PrintLogger() FileLogger()
小華的改動(dòng)其實(shí)就是利用了依賴倒置原則阳距,增強(qiáng)了易修改性塔粒、擴(kuò)展性、可維護(hù)性等筐摘。
細(xì)心的朋友其實(shí)還發(fā)現(xiàn)了卒茬,在改成FileLogger的時(shí)候,Client多余地調(diào)用了FileLogger的start方法咖熟。這就是依賴細(xì)節(jié)圃酵,暴露細(xì)節(jié),引起的問(wèn)題馍管。而使用抽象的接口就能較好地避免這類(lèi)問(wèn)題郭赐。
注意點(diǎn)
分清細(xì)節(jié)與抽象
雖然依賴倒置原則有很大的好處,但也不是所有的類(lèi)都需要有抽象一個(gè)接口去對(duì)應(yīng)确沸,要視情況而定捌锭。變量的聲明類(lèi)型盡量是抽象類(lèi)或接口
注意是盡量,而不是全部罗捎。盡量不要覆寫(xiě)基類(lèi)的方法
如果基類(lèi)是一個(gè)抽象類(lèi)观谦,而這個(gè)方法已經(jīng)實(shí)現(xiàn)了,子類(lèi)盡量不要覆寫(xiě)桨菜。類(lèi)間依賴的是抽象豁状,覆寫(xiě)了抽象方法,對(duì)依賴的穩(wěn)定性會(huì)有一定的影響倒得。繼承要遵循里氏替換原則