1. 何為接口隔離原則
定義:客戶端不應該依賴它不需要的接口;一個類對另一個類的依賴應該建立在最小的接口上。
定義解讀:
該定義包含以下三層含義:
a. 一個類對另一個類的依賴應該建立在最小的接口上;
b. 一個接口代表一個角色,不應該將不同的角色都交給一個接口,因為這樣可能會形成一個臃腫的大接口;
b. 不應該強迫客戶端依賴他們從來不用的方法。
接口隔離原則有點像單一職責原則,但是也有區別,在單一職責原則中,一個接口可能有多個方法,提供給多種不同的調用者所調用,但是它們始終完成同一種功能,因此它們符合單一職責原則,卻不符合接口隔離原則,因為這個接口存在著多種角色,因此可以拆分成更多的子接口,以供不同的調用者所調用。比如說,項目中我們通常有一個Web服務管理的類,接口定義中,我們可能會將所有模塊的數據調用方法都在接口中進行定義,因為它們都完成的是同一種功能:和服務器進行數據交互;但是對于具體的業務功能模塊來說,其他模塊的數據調用方法它們從來不會使用,因此不符合接口隔離原則。
優點
使用接口隔離原則,意在設計一個短而小的接口和類,符合我們常說的高內聚低耦合的設計思想,從而使得類具有很好的可讀性、可擴展性和可維護性。
2. 情景設置
類A通過接口I依賴類B,類C通過接口I依賴類D,如果接口I對于類A和類C來說不是最小接口,而類B和類D必須去實現它們不需要的方法。下面通過一個UML圖來說明這種現象,如圖2-1所示:
在這里,我們定義了一個動物活動的接口IAnimal,里面有4個方法:飛行fly、行走walk、吃eat和工作work,然后分別用人類People和鳥類Bird實現了這個接口。中國人類ChinesePeople和鸚鵡類Parrot通過接口IAnimal分別依賴類People和類Bird。很明顯,對于ChinesePeople來說,fly方法是多余的,因為人不會飛;對于Parrot類來說,work方法是多余的,因為鸚鵡不需要工作。接口IAnimal對于類ChinesePeople和類Parrot來說不是最小接口。
解決方案
將臃腫的接口IAnimal拆分為獨立的幾個接口,類ChinesePeople和類Parrot分別與它們需要的接口建立依賴關系,也就是采用接口隔離原則。修改后的UML圖如圖2-2所示:
3. 代碼實現
(1)三個接口
@protocol IAnimal <NSObject>
//- (void)work; // 使用接口隔離原則,將該方法放到其他接口
//- (void)fly; // 使用接口隔離原則,將該方法放到其他接口
@optional
- (void)walk;
@required
- (void)eat;
@end
@protocol IPeople <NSObject>
- (void)work;
@end
@protocol IBird <NSObject>
- (void)fly;
@end
(2)People
@interface People : NSObject<IAnimal,IPeople>
@end
@implementation People
- (void)work
{
NSLog(@"I am working");
}
//- (void)fly
//{
// NSLog(@"I am flying");
//}
- (void)walk
{
NSLog(@"I am walking");
}
- (void)eat
{
NSLog(@"I am eating");
}
@end
(3)Bird
@interface Bird : NSObject<IAnimal,IBird>
@end
@implementation Bird
//- (void)work
//{
// NSLog(@"I am working");
//}
- (void)fly
{
NSLog(@"I am flying");
}
- (void)walk
{
NSLog(@"I am walking");
}
- (void)eat
{
NSLog(@"I am eating");
}
@end
(4)ChinesePeople
@interface ChinesePeople : NSObject
@property (nonatomic,assign) id<IAnimal> animalDelegate;
@property (nonatomic,assign) id<IPeople> peopleDelegate;
@end
@implementation ChinesePeople
@synthesize animalDelegate = _animalDelegate;
@synthesize peopleDelegate = _peopleDelegate;
- (void)work
{
// [_animalDelegate work];
[_peopleDelegate work];
}
//- (void)fly
//{
// [_animalDelegate fly];
//}
- (void)walk
{
[_animalDelegate walk];
}
- (void)eat
{
[_animalDelegate eat];
}
@end
(5)Parrot
@interface Parrot : NSObject
@property (nonatomic,assign) id<IAnimal> animalDelegate;
@property (nonatomic,assign) id<IBird> birdDelegate;
@end
@implementation Parrot
@synthesize animalDelegate = _animalDelegate;
@synthesize birdDelegate = _birdDelegate;
//- (void)work
//{
// [_animalDelegate work];
//}
- (void)fly
{
// [_animalDelegate fly];
[_birdDelegate fly];
}
- (void)walk
{
[_animalDelegate walk];
}
- (void)eat
{
[_animalDelegate eat];
}
@end
(6)客戶端調用
ChinesePeople *chinesePeople = [[ChinesePeople alloc] init];
People *people = [[People alloc] init];
chinesePeople.animalDelegate = people;
chinesePeople.peopleDelegate = people;
[chinesePeople work];
[chinesePeople eat];
[chinesePeople walk];
從UML圖可以看到,遵守接口隔離原則,會使代碼量增加不少,源碼中也是這樣;實際上,iOS在定義協議的時候,可以設置方法為可選實現(@optional)和必須實現(@required,默認值),我們可以設置work方法和fly方法為可選實現的方法,這樣在類People和類Bird中,這兩個方法可以根據需要來決定是否實現。采用這種方式,功能上實現是沒有問題,對于簡單的接口來說,也便于維護和管理。但是,當方法隨著業務需求的增加而不斷增加的話,如果我們不應用接口隔離原則,那么就可能形成一個龐大臃腫的接口,這樣的接口的可維護性和重用性是很差的。因此,我們應該盡量細化接口,本篇將一個接口變更為3個專用的接口所采用的就是接口隔離原則。在項目開發中,依賴幾個專用的接口要比依賴一個綜合的接口更加靈活。通過分散定義多個接口,可以預防外來變更的擴散,提高系統的靈活性和可維護性。
雖然接口隔離原則很有意義,但在實際項目中,應該注意度的把握,接口設計的過大或過小都不好,應該根據實際情況多思考再進行設計。