依賴(lài)注入(Dependency Injection)
今天我們討論的內(nèi)容核心是面向接口編程,我決定還是要從依賴(lài)注入開(kāi)始講起,因?yàn)镈I的思想可以說(shuō)是面向接口編程思想的特殊表現(xiàn),也可以說(shuō)是與面向接口編程相輔相成。
先撇開(kāi)讓人頭腦發(fā)暈的文字定義,我們還是用我們最忠實(shí)和伙伴——代碼來(lái)了解依賴(lài)注入。我們先來(lái)一個(gè)粗略的例子,由淺入深:
我們有一個(gè)公交車(chē)類(lèi)(Bus),每天早上6點(diǎn)鐘需要發(fā)車(chē)(work),為其分配對(duì)應(yīng)的司機(jī)(Driver),看代碼
@implementation Bus
- (void)work {
Driver *driver = [[Driver alloc] initWithName:@"張三"];
//dosomething
}
@end
在上面這段代碼中,Bus對(duì)象的運(yùn)作需要用到Driver對(duì)象,因而創(chuàng)建了一個(gè)Driver對(duì)象,我們稱(chēng)Bus對(duì)Driver有一個(gè)依賴(lài)。這樣的強(qiáng)耦合關(guān)系會(huì)因?yàn)槿蘸蟮淖兓o我們帶來(lái)很多麻煩,不久將來(lái)張三師傅辭職了,我們需要修改Bus-work()的代碼,也就是說(shuō)在開(kāi)發(fā)過(guò)程中非常不便于單元測(cè)試(一是不能方便地更換各種Driver對(duì)象,二是如果Driver這個(gè)職位創(chuàng)建是耗時(shí)操作或者高成本操作,我們并不能使用準(zhǔn)備好的Driver實(shí)現(xiàn)快速重復(fù)測(cè)試)。 我們繼續(xù):
@implementation Bus
@property (strong, nonatomic) Driver *driver;
- (instancetype)initWithDriver:(Driver *)driver {
self = [super init];
if (self) {
self.dirver = driver;
}
return self;
}
- (void)work {
//dosomething
}
@end
以上這段代碼我們通過(guò)init方法,為Bus對(duì)象傳入了一個(gè)Driver對(duì)象,像這種非自己主動(dòng)初始化依賴(lài),而從外部通過(guò)注入點(diǎn)注入依賴(lài)的方式,我們就稱(chēng)為依賴(lài)注入,而例子中的這種注入的方法稱(chēng)之為構(gòu)造器注入。明顯的,這個(gè)場(chǎng)景中Bus和Driver的耦合因此輕了一層。說(shuō)到解耦,并不是說(shuō)Bus和Driver之間的依賴(lài)關(guān)系就不存在了,在Bus的范圍內(nèi)看來(lái),只是將依賴(lài)建立從編譯期間推遲到了運(yùn)行期間,畢竟Bus無(wú)論如何也是需要Driver提供服務(wù)的。對(duì)此,這篇文章有一個(gè)非常形象的比喻,“依賴(lài)就像是系統(tǒng)中的plugin,主系統(tǒng)并不強(qiáng)依賴(lài)于任何一個(gè)插件,但一旦插件被加載,主系統(tǒng)就應(yīng)該可以準(zhǔn)確調(diào)用適當(dāng)插件的功能”。
類(lèi)似這樣的注入方式還有
- 屬性注入
- 方法注入
- 環(huán)境上下文注入
- 子類(lèi)重寫(xiě)方法注入等
不同的只是注入的手段,思想還是一樣的。
輕輕地思考
例子說(shuō)完了,那是不是說(shuō)我們對(duì)所有的依賴(lài)都要這樣一視同仁,破壞程序的封裝性而減輕所有的依賴(lài)呢?不,這僅僅是讓我們認(rèn)識(shí)依賴(lài)注入的思想。但是對(duì)于測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(TDD),一定量的依賴(lài)抽取又是必須的。如果說(shuō)實(shí)在不希望把那么多的拉環(huán)暴露出來(lái),又必須貫徹測(cè)試驅(qū)動(dòng)開(kāi)發(fā),objc的這篇文章這么說(shuō)到:
"This can be done by declaring them in a class category in a separate header file. For example, if we’re dealing with Example.h, then create an additional header ExampleInternal.h. This will be imported only by Example.m and by test code."
我們可以通過(guò)強(qiáng)大的Category,將注入的針口放在Category中,而對(duì)應(yīng)的Category放在一個(gè)專(zhuān)門(mén)用來(lái)測(cè)試的header中,思考下這個(gè)Category中做了什么?swizzle掉依賴(lài)所在的方法,并且執(zhí)行依賴(lài)注入,當(dāng)然這兩者是分開(kāi)的。
看到這里,是不是有點(diǎn)覺(jué)得DI完全就是為了單測(cè)服務(wù)?我以前也是這么認(rèn)為的,其實(shí)不然,這僅僅是一個(gè)簡(jiǎn)單介紹DI思想的一個(gè)例子,層次不同,我們不能從中體驗(yàn)到DI帶來(lái)的好處。
組件化
也是objc的那篇文章中提到一種叫做“pluggable排插思想”
,用原話來(lái)說(shuō),如果一個(gè)類(lèi)的initializer需要提供一個(gè)id<foo>
的參數(shù),說(shuō)明我們需要為之提供一個(gè)遵守foo協(xié)議的對(duì)象才可以讓這個(gè)類(lèi)運(yùn)作起來(lái),有沒(méi)有發(fā)現(xiàn)DI外衣下的面向接口思想
的肉體?所以說(shuō)更深層的,DI的一個(gè)目標(biāo)是為了實(shí)現(xiàn)組件化架構(gòu),DI讓依賴(lài)更加明顯,DI劃定了組件的邊界和組件的組裝方式。
開(kāi)閉原則(Open-Closed Principle)
這里要帶入一個(gè)比較重要的思想——OCP,國(guó)內(nèi)比較少筆墨對(duì)OCP思想的介紹和強(qiáng)調(diào),他的原文解釋是Software entities should be open for extension,but closed for modification,對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。也就是說(shuō)我們對(duì)模塊的設(shè)計(jì),應(yīng)該滿足將來(lái)在不可修改源代碼的情況下對(duì)模塊的職能擴(kuò)展,或者改變模塊的行為。單單這句話就能表現(xiàn)出OCP可怕的地位,他迫使我們主動(dòng)考慮了將來(lái),使應(yīng)用保證了核心代碼的穩(wěn)定性和對(duì)新需求的靈活性。
依賴(lài)獲取(Dependency Locate)
上面我們理解了依賴(lài)注入的基礎(chǔ)思想,讓依賴(lài)顯式化,為依賴(lài)提供合適的注入點(diǎn)(針口),提升程序的靈活性。帶來(lái)的結(jié)果就是當(dāng)我們需要更換依賴(lài)的時(shí)候不需要對(duì)使用服務(wù)的類(lèi)(姑且叫作客戶類(lèi))作代碼修改,將提供服務(wù)的類(lèi)(服務(wù)類(lèi))由注入點(diǎn)注入到客戶類(lèi)中,耦合的確輕了一層,也符合OCP原則,ok現(xiàn)在我們往外跳一層,在實(shí)例化客戶類(lèi)的角色上下文中,需要實(shí)例化服務(wù)類(lèi)進(jìn)而完成對(duì)客戶類(lèi)注入,服務(wù)類(lèi)的更變必然導(dǎo)致此處代碼的修改,這時(shí)OCP又要站出來(lái)打差評(píng)。
此時(shí)有必要講下依賴(lài)獲取
。既然有注入,當(dāng)然也應(yīng)該有獲取,但這兩者并不是先后執(zhí)行的兩個(gè)過(guò)程,而是相同目的的同一種操作,換句話說(shuō),我們讓客戶類(lèi)由被動(dòng)注入轉(zhuǎn)換成主動(dòng)獲取,繼續(xù)貫徹的仍然是依賴(lài)注入思想。
DL就是在系統(tǒng)中配置一個(gè)獲取點(diǎn),客戶類(lèi)依賴(lài)于服務(wù)類(lèi)的接口而不直接依賴(lài)服務(wù)類(lèi),客戶類(lèi)根據(jù)自身需要從獲取點(diǎn)主動(dòng)獲取服務(wù)類(lèi)為其提供服務(wù)。理解了DI,對(duì)DL的概念肯定是迎刃而解。
我們思考下,客戶類(lèi)只知道獲取點(diǎn),按照道上的規(guī)矩交貨的對(duì)方的身份完全不需要去了解,有沒(méi)有發(fā)現(xiàn)面向接口(POP)的內(nèi)體又暴露了一點(diǎn)?
更高級(jí)的依賴(lài)注入
認(rèn)識(shí)完DI的另一種方式依賴(lài)獲取后,做依賴(lài)注入的辦法就不僅僅局限于上文列舉的幾種最基本的依賴(lài)注入方式。目前比較主流的有配置文件依賴(lài)注入
,反射依賴(lài)注入
,例如java中強(qiáng)大的Spring
和移植到.NET平臺(tái)的Spring.NET
,.NET中自己的Autofac
,他們是結(jié)合配置文件和反射工作的,而oc中的objection
我看了下是通過(guò)key-Value內(nèi)存容器來(lái)做的DI,如果我自己做的話,還可以使用runtime target-action方式(類(lèi)似于其他語(yǔ)言的反射),而重型項(xiàng)目中需不需要用到NSInvocation筆者缺乏這方面的經(jīng)驗(yàn)不敢獨(dú)斷。
下面還是用一個(gè)簡(jiǎn)單的例子來(lái)增強(qiáng)對(duì)通過(guò)配置文件做依賴(lài)獲取的認(rèn)識(shí):
最近有看qq瀏覽器莊延軍老師關(guān)于內(nèi)存管理的公開(kāi)課,就用手Q瀏覽器更換主題打一個(gè)例子吧:
//定義一個(gè)主題接口,讓所有主題都實(shí)現(xiàn)它
@protocol ItfThemeFactory <NSObject>
- (void)drawing;
@end
//主題
@implementation SpringFactory <ItfThemeFactory>
- (void)drawing {
//drawing theme...
}
@end
@implementation SummerFactory <ItfThemeFactory>
- (void)drawing {
//drawing theme...
}
@end
//主題工廠Animator
@interface ThemeFactoryAnimator : NSObject
@property (strong, nonatomic) id<ItfThemeFactory> themeFactory;
@end
@implementation ThemeFactoryAnimator
- (id<ItfTheme>)themeFactory {
NSString *path = [[NSBundle mainBundle] pathForResource:@"theme" ofType:@"plist"];
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
NSString *theme = [dict objectForKey:@"theme"];
if ([theme isEqualToString:@"spring"]) {
_themeFactory = [[SpringFactory alloc] init];
} else if ([theme isEqualToString:@"summer"]) {
_themeFactory = [[SummerFactory alloc] init];
} else {
//assert
}
}
@end
//在執(zhí)行方法里我們要做什么?
- (void)work {
ThemeFactoryAnimator *tfAnimator = [ThemeFactoryAnimator alloc] init];
id<ItfThemeFactory> themeFactory = tfAnimator.themeFactory;
[themeFactory drawing];
}
以上,我們只需要在執(zhí)行方法(-work())中拿到themeFactory,對(duì)界面進(jìn)行渲染即可,而原本有可能出現(xiàn)依賴(lài)的地方——ThemeFactoryAnimator已經(jīng)不依賴(lài)于外部注入,而僅僅依賴(lài)于我的theme.plist配置文件,也可以說(shuō)我們將多態(tài)封裝到了這個(gè)“獲取點(diǎn)”內(nèi),因此主題的改變映射到了配置文件中對(duì)應(yīng)內(nèi)容的改變,但是這個(gè)更換主題系統(tǒng)目前就利用DI變得符合OCP原則了嗎?不是的,雖然依賴(lài)的改變已經(jīng)映射到了客戶類(lèi)封裝的外部——配置文件中,可是我們還是無(wú)法避免if-else結(jié)構(gòu)的存在,我們可以不修改代碼自由更換主題,可是如果又開(kāi)發(fā)出了一套新的主題呢?這個(gè)系統(tǒng)對(duì)于未來(lái)還是無(wú)能為力,這一part的重點(diǎn)是依賴(lài)獲取,至于怎么消除這種缺陷?看完這篇文章也許你就自然明白了。
面向接口編程(Protocol-oriented programming)
我們是時(shí)候談?wù)劽嫦蚪涌诹耍绻麑?duì)筆者上面說(shuō)的還沒(méi)能很好理解沒(méi)關(guān)系,思想的認(rèn)識(shí)需要時(shí)間去沉淀、矯正,出來(lái)的才是真理。
首先我們?cè)鯓佣x接口:“接口泛指實(shí)體把自己提供給外界的一種抽象化物,用以由內(nèi)部操作分離出外部溝通方法,使其能被修改內(nèi)部而不影響外界其他實(shí)體與其交互的方式”,換句話說(shuō),在我們程序的世界里,接口的作用就是用于定義一個(gè)或一組規(guī)則,實(shí)現(xiàn)對(duì)應(yīng)接口的實(shí)體需要遵守對(duì)應(yīng)的這些規(guī)則。也可以說(shuō)是對(duì)“同類(lèi)事物”的抽象表示,而“同類(lèi)事物”的界定就看是否實(shí)現(xiàn)了同一個(gè)接口,譬如有一個(gè)Animal接口和一個(gè)NightWorking接口,公雞實(shí)現(xiàn)了Animal接口,貓頭鷹實(shí)現(xiàn)了Animal接口和NightWorking接口,還有一個(gè)實(shí)現(xiàn)了NightWorking接口的路燈,在Animal的范疇下,我們可以稱(chēng)公雞和貓頭鷹是同類(lèi)事物,而在NightWorking的范疇下,我們可以稱(chēng)貓頭鷹和路燈是同類(lèi)事物。。。。相對(duì)的東西真恐怖,不知道筆者什么時(shí)候會(huì)跟什么東西被劃分為同類(lèi)。。。
面向接口編程(編碼)
面向接口比較抽象,也比較廣泛,它不僅僅是指一個(gè)定性的東西,我們可以從POP為程序帶來(lái)的一個(gè)一個(gè)優(yōu)越性為切入點(diǎn)研究,下面繼續(xù)是一個(gè)簡(jiǎn)單的例子,讓我們來(lái)感受下POP思想的初衷:
這次還是拿交通工具來(lái)說(shuō),
//首先我們定義一個(gè)交通工具接口
@protocol Transportation <NSObject>
- (void)drinking;
- (void)freight;
@end
//還有一個(gè)發(fā)光體接口
@protocol Irradiative <NSObject>
- (void)shine;
@end
//當(dāng)然drinking就代表補(bǔ)需,汽車(chē)飛機(jī)的內(nèi)部實(shí)現(xiàn)就是加油,馬牛的內(nèi)部實(shí)現(xiàn)就是吃草喝水什么的。freight就是裝載
//當(dāng)上帝創(chuàng)造馬的時(shí)候,讓馬遵守并實(shí)現(xiàn)這個(gè)接口:
@implementation Horse <Transportation>
- (void)drinking {
//吃草,喝果汁
}
- (void)freight {
//停住腳步,或者半蹲,讓友好的人類(lèi)騎上去
}
@end
//當(dāng)人類(lèi)創(chuàng)造飛機(jī)的時(shí)候,慘了,不知道去哪里找上帝溝通,又怕疏漏了什么影響了這個(gè)世界的運(yùn)行規(guī)律?沒(méi)事上帝給我們留下了Transportation接口,而且飛機(jī)同時(shí)還要遵守發(fā)光體接口Irradiative,于是:
@implementation Aircraft <Transportation, Irradiative>
- (void)drinking {
//加油, 92的
}
- (void)freight {
//降落,熄火,開(kāi)艙門(mén)
}
- (void)shine {
//燃燒汽油,生物質(zhì)能轉(zhuǎn)化成電能,照你
}
@end
當(dāng)然物理學(xué)上能通過(guò)轉(zhuǎn)化其他物質(zhì)發(fā)出可見(jiàn)光的也不一定叫發(fā)光體,已經(jīng)畢業(yè)了就容我不按規(guī)矩來(lái)吧。以上,因?yàn)槲覀儼匆?guī)矩辦事,制造出來(lái)的飛機(jī)從來(lái)沒(méi)有自爆過(guò)。而馬匹也重來(lái)不需要死機(jī)重啟。感覺(jué)很有道理!如果不久將來(lái)我們著手創(chuàng)造時(shí)空穿梭機(jī),我們第一步工作,就是要讓其遵守實(shí)現(xiàn)Transportation接口等,如果我們要求這個(gè)穿梭機(jī)還能幫我們敲代碼,我們繼續(xù)讓其遵守objcAble接口。
面向接口編程(架構(gòu))
不知不覺(jué)文章篇幅已經(jīng)比較大了,讓我們來(lái)再往上爬一層,讓POP應(yīng)用于更大的一個(gè)領(lǐng)域,甚至改變架構(gòu),雖然上一part已經(jīng)算是一種架構(gòu)思想,但是筆者更希望表現(xiàn)的是他在編碼應(yīng)用中的優(yōu)越性,而這一part將賦予POP在大型項(xiàng)目中不可撼動(dòng)的地位。
無(wú)論是哪種架構(gòu)方式,層次關(guān)系肯定是撇不開(kāi)的,并且層次關(guān)系也代表著一種架構(gòu)的主心骨,無(wú)論業(yè)務(wù)分層,功能分層,還是角色分層,存在于各個(gè)位置的依賴(lài)關(guān)系都需要我們?nèi)フ暎鳳OP的目的正是為了化解這些強(qiáng)依賴(lài),打破上層實(shí)例化下層去為其提供服務(wù)的強(qiáng)耦合,在大型項(xiàng)目中,一層的變化可能會(huì)聯(lián)動(dòng)1+N層,這樣的變化是致命的,正如上文我們提到過(guò)的,讓一個(gè)實(shí)體由依賴(lài)另一個(gè)實(shí)體,轉(zhuǎn)變成依賴(lài)一個(gè)接口,將被依賴(lài)實(shí)體的變化隔絕于接口之外。
補(bǔ)充一句,這里的接口指代的并不是上一part中實(shí)體化的"接口",而是相對(duì)意義上的接口,一種思想!
iOS面向接口編程架構(gòu) 實(shí)現(xiàn)無(wú)耦合開(kāi)發(fā)方式
不知道大家看了“面向接口編程(編碼)”后,有沒(méi)有發(fā)現(xiàn)日常OC編碼中似乎隨處可見(jiàn)接口編程的痕跡?——侵蝕了我們項(xiàng)目各個(gè)模塊的代理模式,代理模式的工作原理就是,一方使用protocol(接口)劃定一個(gè)或一組規(guī)則,成為其代理的角色必須遵守這一系列規(guī)則,最后根據(jù)規(guī)則去辦事,好處依然是那么明顯,主體并不需要與代理溝通,代理也不需要做多余的培訓(xùn),直接上崗,從這里又強(qiáng)化了一遍接口即一種由內(nèi)部操作分離出外部溝通方法,而核心就是一系列規(guī)則,通過(guò)接口工作,比直接訪問(wèn)屬性或者方法穩(wěn)健得多。
而這一part中我們的主題并不是這個(gè),為了思想上的升華,這里給出一個(gè)簡(jiǎn)單例子,這里例子參考龐海礁師兄文章例子變換而來(lái),講到那種相對(duì)意義上的接口思想。
#pragma mark - 面向?qū)ο髠鹘y(tǒng)的方式:
//服務(wù)實(shí)現(xiàn)者 甲方 ,編寫(xiě)一個(gè)服務(wù)類(lèi)
@interface MusicLoadingProtocolObj()
@end
@implementation MusicLoadingProtocolObj
- (void)requestWithUrl:(NSURL *)url Param:(NSDictionary *)param {
//do something
}
@end
//服務(wù)使用者 乙方 ,通過(guò)接口獲取服務(wù)類(lèi)
#import "MusicLoadingProtocolObj.h"
@interface Client()
@end
@implementation Client
- (void)work {
MusicLoadingProtocolObj *musicLoadingProtocolObj = [MusicLoadingProtocolObj alloc] init];
[musicLoadingProtocolObj requestWithUrl:url Param:param];
}
@end
//當(dāng)然,在這里我們已經(jīng)應(yīng)用了構(gòu)造器注入的DI思想。或者我們?nèi)绻褂脤傩宰⑷耄磕敲串?dāng)然就沒(méi)那么直觀,沒(méi)有貫徹接口編程的思想。
#pragma mark - 接下來(lái)就是面向接口(POP)的做法:
//首先,定義一個(gè)ServiceProtocol
@protocol MusicLoadingProtocol <NSObject>
- (void)requestWithUrl:(NSURL *)url Param:(NSDictionary *)param;
@end
//甲方
@interface MusicLoadingProtocolObj() <MusicLoadingProtocol>
@end
@implementation MusicLoadingProtocolObj
- (void)requestWithUrl:(NSURL *)url Param:(NSDictionary *)param {
//do something
}
@end
//乙方
@interface Client()
@end
@implementation Client
- (void)work {
id<MusicLoadingProtocol> service = [[JSObjection defaultInjector] getObject:@protocol(MusicLoadingProtocol)];
[service requestWithUrl:url Param:param];
}
@end
上例中筆者借助了OC的一個(gè)輕量級(jí)的DI框架objection,服務(wù)實(shí)現(xiàn)者甲方獨(dú)立編寫(xiě)服務(wù)實(shí)現(xiàn),而后將服務(wù)通過(guò)objection綁定到protocol之上,去看看服務(wù)使用者,乙方利用objection通過(guò)protocol拿到服務(wù)類(lèi)實(shí)例,根據(jù)protocol中定義的規(guī)則,馬上就實(shí)現(xiàn)了服務(wù)。不需要import,不需要實(shí)例化,高度解耦,并且符合OCP原則。objection的原理就是上文提到的key-value內(nèi)存映射表,對(duì)于大型項(xiàng)目,多小組分項(xiàng)目開(kāi)發(fā)再合并的生產(chǎn)線,POP是必不可少的。
如果說(shuō)我們?cè)谳p型開(kāi)發(fā)中不想使用框架,我們也可以談?wù)勛约簩?shí)現(xiàn)POP+DI,利用起OC的利器——runtime。其實(shí)在上例已經(jīng)埋下伏筆,這次我們的乙方可以這樣做:
//乙方
@interface Client()
@end
@implementation Client
- (void)work {
NSString *clazzName = @"MusicLoadingProtocol";
[clazzName stringByAppendingString:@"Obj"];
Class serviceClazz = NSClassFromString(clazzName);
id<MusicLoadingProtocol> service = [serviceClazz alloc] init];
[service requestWithUrl:url Param:param];
}
@end
就是這樣,甲乙雙發(fā)約定了以接口名+Obj字符串的規(guī)則去定義服務(wù)類(lèi),乙方做DL時(shí)只需要配合runtime,也是輕而易舉。
那么如果服務(wù)類(lèi)實(shí)例化需要參數(shù)呢?
配置文件能解決這個(gè)問(wèn)題,上文有提到Spring框架做DI的原理就是反射+xml
,一般來(lái)說(shuō)大部分支持反射機(jī)制語(yǔ)言的DI框架原理都是相似的,這里說(shuō)下筆者了解的兩種主流注入原理,構(gòu)造器注入和屬性注入,記得上文也提到過(guò)著兩種注入方式,筆者強(qiáng)調(diào)過(guò)那只是一種思想,不是定性的一種方法,ok來(lái)看下那些DI框架是怎樣做的。
- 構(gòu)造器注入
在進(jìn)行依賴(lài)獲取的時(shí)候,DI框架通過(guò)反射機(jī)制得到待創(chuàng)建類(lèi)的構(gòu)造方法,然后根據(jù)構(gòu)造器所需參數(shù)的類(lèi)型或者順序,在DI容器節(jié)點(diǎn)中尋找,然后提供參數(shù),創(chuàng)建實(shí)例。 - 屬性注入
同樣的,在進(jìn)行DL時(shí),通過(guò)反射得到待創(chuàng)建類(lèi)型的所有屬性,然后根據(jù)屬性在DI容器節(jié)點(diǎn)中進(jìn)行匹配,有則創(chuàng)建提供,無(wú)則跳過(guò)。
最后利用詞條做個(gè)局部總結(jié):
- 依賴(lài)注入+接口編程
- 調(diào)用者無(wú)須關(guān)心對(duì)象任何實(shí)現(xiàn),只需按照接口規(guī)則調(diào)用服務(wù)
- 在系統(tǒng)分析和架構(gòu)中,分清層次和依賴(lài)關(guān)系,每個(gè)層次不是直接向其上層提供服務(wù)(即不是直接實(shí)例化在上層中),而是通過(guò)定義一組接口,僅向上層暴露其接口功能,上層對(duì)于下層僅僅是接口依賴(lài),而不依賴(lài)具體類(lèi)。
- 服務(wù)使用端由對(duì)對(duì)象的依賴(lài)轉(zhuǎn)變成對(duì)接口的依賴(lài),這樣甚至可以在服務(wù)提供對(duì)象還未存在之前編碼(分子項(xiàng)目開(kāi)發(fā))
End
依賴(lài)注入只是一種思想,其實(shí)也就是一個(gè)過(guò)程,依賴(lài)注入用到了面向接口的編程思想,面向接口的架構(gòu)實(shí)現(xiàn)用到了依賴(lài)注入的執(zhí)行方式。而面向接口編程和面向?qū)ο缶幊滩⒉皇瞧郊?jí)的,它并不是比面向?qū)ο缶幊谈冗M(jìn)的一種獨(dú)立的編程思想,而是附屬于面向?qū)ο笏枷塍w系,屬于其中一部分。或者說(shuō),它是面向?qū)ο缶幊腆w系中的思想精髓之一。
同時(shí)我要贊嘆POP的強(qiáng)大,對(duì)于未來(lái)的未知事物,我們先認(rèn)知這個(gè)東西的行為(使用接口來(lái)實(shí)現(xiàn)這個(gè)行為),再認(rèn)知這種行為的具體(使用具體的代碼實(shí)現(xiàn)這個(gè)接口)。
這篇文章中有提到廣義的"接口"也有專(zhuān)指的"接口",讀后具體的理解和認(rèn)識(shí)就靠自己用時(shí)間去慢慢沉淀了。
寫(xiě)在最后
在我對(duì)依賴(lài)注入理解得比較淺的時(shí)候,只是淺層地理解這種思想的存在,并沒(méi)有相關(guān)開(kāi)發(fā)經(jīng)驗(yàn)足以支撐我深入對(duì)DI的思考和感受,網(wǎng)上的文章全部都僅僅局限在那幾個(gè)淺層的例子,并沒(méi)有繼續(xù)深入挖掘解釋?zhuān)恳晃粠熜譃槲抑v解,所以我希望有篇文章可以聚集大家思考討論,同時(shí)為他人提供學(xué)習(xí)的途徑。心中有疑惑又無(wú)能為力的感覺(jué)的確非常痛苦。謝謝!