1. 何為依賴倒置原則
定義:高層模塊不應該依賴于低層模塊缠诅,二者都應該依賴于抽象;抽象不應該依賴細節(jié)乍迄;細節(jié)應該依賴抽象管引。
定義解讀:
依賴倒置原則在程序編碼中經(jīng)常運用,其核心思想就是面向接口編程闯两,高層模塊不應該依賴低層模塊(原子操作的模塊)褥伴,兩者都應該依賴于抽象。我們平時常說的“針對接口編程漾狼,不要針對實現(xiàn)編程”就是依賴倒轉原則的最好體現(xiàn):接口(也可以是抽象類)就是一種抽象重慢,只要不修改接口聲明,大家可以放心大膽調用邦投,至于接口的內(nèi)部實現(xiàn)則無需關心伤锚,可以隨便重構。這里志衣,接口就是抽象屯援,而接口的實現(xiàn)就是細節(jié)。
如果不管高層模塊還是底層模塊念脯,它們都依賴于抽象狞洋,具體一點就是接口或者抽象類,只要接口是穩(wěn)定的绿店,那么任何一個的更改都不用擔心其他受到影響吉懊,這就使得無論高層模塊還是低層模塊都可以很容易地被復用。
依賴倒轉原則其實可以說是面向對象設計的標志假勿,用哪種語言來編寫程序不重要借嗽,如果編寫時考慮的都是如何針對抽象編程而不是針對細節(jié)編程,即程序中所有的依賴關系都是終止于抽象類或者接口转培,那就是面向對象的設計恶导,反之那就是過程化的設計(說這句話可能不怎么好理解,再加上一句話就好理解了:面向對象的設計浸须,出發(fā)點就是應對變化的問題)惨寿。
再舉一個生活中的例子邦泄,電腦中內(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)點:代碼結構清晰或辖,維護容易瘾英。
2. 情景設置
類A直接依賴類B,假如需要將類A改為依賴類C颂暇,則必須通過修改類A的代碼來達成缺谴。這種場景下,類A一般是高層模塊耳鸯,負責復雜的業(yè)務邏輯湿蛔;類B和類C是低層模塊,負責基本的原子操作县爬;假如修改類A阳啥,會給程序帶來不必要的風險。
解決方案
將類A修改為依賴接口I财喳,類B和類C各自實現(xiàn)接口I察迟,類A通過接口I間接與類B或者類C發(fā)生聯(lián)系,則會大大降低修改類A的幾率耳高。
依賴倒置原則基于這樣一個事實:相對于細節(jié)的多變性扎瓶,抽象的東西要穩(wěn)定的多。以抽象為基礎搭建起來的架構比以細節(jié)為基礎搭建起來的架構要穩(wěn)定的多泌枪。在C#/Java中概荷,抽象指的是接口或者抽象類;在Objective-C中工闺,抽象指的是委托乍赫,細節(jié)就是具體的實現(xiàn)類瓣蛀,使用接口或者抽象類的目的是制定好規(guī)范和契約,而不去涉及任何具體的操作雷厂,把展現(xiàn)細節(jié)的任務交給它們的實現(xiàn)類去完成惋增。
3. 代碼示例
繼續(xù)發(fā)工資的場景:這里,類SalaryManage(類似上面說的類A)負責工資的管理改鲫;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)客戶端調用
/* 原始做法
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)在假設需要給經(jīng)理發(fā)工資稽亏,我們發(fā)現(xiàn)工資管理類SalaryManage沒法直接完成這個功能,需要我們添加新的方法缕题,才能完成截歉。再假設我們還需要給普通員工、財務總監(jiān)烟零、研發(fā)總監(jiān)等更多的崗位發(fā)送工資瘪松,那么我們就只能不斷的去修改SalaryManage類來滿足業(yè)務的需求。產(chǎn)生這種現(xiàn)象的原因就是SalaryManage與Director之間的耦合性太高了锨阿,必須降低它們之間的耦合度才行宵睦。因此我們引入一個委托EmployeeDelegate,它提供一個發(fā)放工資的方法定義墅诡,然后我們讓具體的員工類Director壳嚎、Manager等都實現(xiàn)該委托方法。
這樣修改后末早,無論以后怎樣擴展其他的崗位烟馅,都不需要再修改SalaryManage類了。代表高層模塊的SalaryManage類將負責完成主要的業(yè)務邏輯(發(fā)工資)荐吉,如果需要對SalaryManage類進行修改焙糟,引入錯誤的風險極大。所以遵循依賴倒置原則可以降低類之間的耦合性样屠,提高系統(tǒng)的穩(wěn)定性穿撮,降低修改程序造成的風險。
同樣痪欲,采用依賴倒置原則給多人并行開發(fā)帶來了極大的便利悦穿,比如在上面的例子中,剛開始SalaryManage類與Director類直接耦合時业踢,SalaryManage類必須等Director類編碼完成后才可以進行編碼和測試栗柒,因為SalaryManage類依賴于Director類。按照依賴倒置原則修改后,則可以同時開工瞬沦,互不影響太伊,因為SalaryManage與Director類一點關系也沒有,只依賴于協(xié)議(Java和C#中稱為接口)EmployeeDelegate逛钻。參與協(xié)作開發(fā)的人越多僚焦、項目越龐大,采用依賴導致原則的意義就越重大曙痘。