1. 何為依賴倒置原則
定義:高層模塊不應(yīng)該依賴于低層模塊,二者都應(yīng)該依賴于抽象;抽象不應(yīng)該依賴細節(jié);細節(jié)應(yīng)該依賴抽象。
定義解讀:
依賴倒置原則在程序編碼中經(jīng)常運用,其核心思想就是面向接口編程,高層模塊不應(yīng)該依賴低層模塊(原子操作的模塊),兩者都應(yīng)該依賴于抽象。我們平時常說的“針對接口編程,不要針對實現(xiàn)編程”就是依賴倒轉(zhuǎn)原則的最好體現(xiàn):接口(也可以是抽象類)就是一種抽象,只要不修改接口聲明,大家可以放心大膽調(diào)用,至于接口的內(nèi)部實現(xiàn)則無需關(guān)心,可以隨便重構(gòu)。這里,接口就是抽象,而接口的實現(xiàn)就是細節(jié)。
如果不管高層模塊還是底層模塊,它們都依賴于抽象,具體一點就是接口或者抽象類,只要接口是穩(wěn)定的,那么任何一個的更改都不用擔(dān)心其他受到影響,這就使得無論高層模塊還是低層模塊都可以很容易地被復(fù)用。
依賴倒轉(zhuǎn)原則其實可以說是面向?qū)ο笤O(shè)計的標志,用哪種語言來編寫程序不重要,如果編寫時考慮的都是如何針對抽象編程而不是針對細節(jié)編程,即程序中所有的依賴關(guān)系都是終止于抽象類或者接口,那就是面向?qū)ο蟮脑O(shè)計,反之那就是過程化的設(shè)計(說這句話可能不怎么好理解,再加上一句話就好理解了:面向?qū)ο蟮脑O(shè)計,出發(fā)點就是應(yīng)對變化的問題)。
再舉一個生活中的例子,電腦中內(nèi)存或者顯卡插槽,其實是一種接口,而這就是抽象;只要符合這個接口的要求,無論是用金士頓的內(nèi)存,還是其它的內(nèi)存,無論是4G的,還是8G的,都可以很方便、輕松的插到電腦上使用。而這些內(nèi)存條就是具體實現(xiàn),就是細節(jié)。
錯誤做法:抽象A依賴于實現(xiàn)細節(jié)b,如圖1-1所示:

正確做法:抽象A依賴于抽象B,實現(xiàn)細節(jié)b實現(xiàn)抽象B,如圖1-2所示:

優(yōu)點:代碼結(jié)構(gòu)清晰,維護容易。
2. 情景設(shè)置
類A直接依賴類B,假如需要將類A改為依賴類C,則必須通過修改類A的代碼來達成。這種場景下,類A一般是高層模塊,負責(zé)復(fù)雜的業(yè)務(wù)邏輯;類B和類C是低層模塊,負責(zé)基本的原子操作;假如修改類A,會給程序帶來不必要的風(fēng)險。
解決方案
將類A修改為依賴接口I,類B和類C各自實現(xiàn)接口I,類A通過接口I間接與類B或者類C發(fā)生聯(lián)系,則會大大降低修改類A的幾率。
依賴倒置原則基于這樣一個事實:相對于細節(jié)的多變性,抽象的東西要穩(wěn)定的多。以抽象為基礎(chǔ)搭建起來的架構(gòu)比以細節(jié)為基礎(chǔ)搭建起來的架構(gòu)要穩(wěn)定的多。在C#/Java中,抽象指的是接口或者抽象類;在Objective-C中,抽象指的是委托,細節(jié)就是具體的實現(xiàn)類,使用接口或者抽象類的目的是制定好規(guī)范和契約,而不去涉及任何具體的操作,把展現(xiàn)細節(jié)的任務(wù)交給它們的實現(xiàn)類去完成。
3. 代碼示例
繼續(xù)發(fā)工資的場景:這里,類SalaryManage(類似上面說的類A)負責(zé)工資的管理;Director(類似上面說的類B)是總監(jiān)類,現(xiàn)在我們要通過SalaryManage類來給總監(jiān)發(fā)放工資了,主要代碼片段如下所示:
(1)EmployeeDelegate
@protocol EmployeeDelegate <NSObject>
- (void)calculateSalary;
@end
(2)Director
@interface Director : NSObject<EmployeeDelegate>
@property(nonatomic, copy) NSString *strName; // 姓名
//- (void)calculateSalary;
@end
@implementation Director
@synthesize strName = _strName;
- (void)calculateSalary
{
NSLog(@"%@總監(jiān)的工資是10000",_strName);
}
@end
(3)Manager
@interface Manager : NSObject<EmployeeDelegate>
@property(nonatomic, copy) NSString *strName; // 姓名
//- (void)calculateSalary;
@end
@implementation Manager
@synthesize strName = _strName;
- (void)calculateSalary
{
NSLog(@"%@經(jīng)理的工資是1000",_strName);
}
@end
(4)SalaryManage
@interface SalaryManage : NSObject
//- (void)calculateSalary:(Director *)director; // 原始做法
- (void)calculateSalary:(id<EmployeeDelegate>)employee; // 遵守依賴倒置原則的做法
@end
@implementation SalaryManage
/*
- (void)calculateSalary:(Director *)director
{
[director calculateSalary];
}
*/
- (void)calculateSalary:(id<EmployeeDelegate>)employee
{
[employee calculateSalary];
}
@end
(5)客戶端調(diào)用
/* 原始做法
Director *director = [[Director alloc] init];
director.strName = @"張三";
SalaryManage *salaryManage = [[SalaryManage alloc] init];
[salaryManage calculateSalary:director];
*/
// 遵守依賴倒置原則的做法
Director *director = [[Director alloc] init];
director.strName = @"張三";
Manager *manager = [[Manager alloc] init];
manager.strName = @"李四";
SalaryManage *salaryManage = [[SalaryManage alloc] init];
[salaryManage calculateSalary:director];
[salaryManage calculateSalary:manager];
這樣給總監(jiān)發(fā)放工資的功能已經(jīng)很好的實現(xiàn)了,現(xiàn)在假設(shè)需要給經(jīng)理發(fā)工資,我們發(fā)現(xiàn)工資管理類SalaryManage沒法直接完成這個功能,需要我們添加新的方法,才能完成。再假設(shè)我們還需要給普通員工、財務(wù)總監(jiān)、研發(fā)總監(jiān)等更多的崗位發(fā)送工資,那么我們就只能不斷的去修改SalaryManage類來滿足業(yè)務(wù)的需求。產(chǎn)生這種現(xiàn)象的原因就是SalaryManage與Director之間的耦合性太高了,必須降低它們之間的耦合度才行。因此我們引入一個委托EmployeeDelegate,它提供一個發(fā)放工資的方法定義,然后我們讓具體的員工類Director、Manager等都實現(xiàn)該委托方法。
這樣修改后,無論以后怎樣擴展其他的崗位,都不需要再修改SalaryManage類了。代表高層模塊的SalaryManage類將負責(zé)完成主要的業(yè)務(wù)邏輯(發(fā)工資),如果需要對SalaryManage類進行修改,引入錯誤的風(fēng)險極大。所以遵循依賴倒置原則可以降低類之間的耦合性,提高系統(tǒng)的穩(wěn)定性,降低修改程序造成的風(fēng)險。
同樣,采用依賴倒置原則給多人并行開發(fā)帶來了極大的便利,比如在上面的例子中,剛開始SalaryManage類與Director類直接耦合時,SalaryManage類必須等Director類編碼完成后才可以進行編碼和測試,因為SalaryManage類依賴于Director類。按照依賴倒置原則修改后,則可以同時開工,互不影響,因為SalaryManage與Director類一點關(guān)系也沒有,只依賴于協(xié)議(Java和C#中稱為接口)EmployeeDelegate。參與協(xié)作開發(fā)的人越多、項目越龐大,采用依賴導(dǎo)致原則的意義就越重大。