目錄
單一職責原則(SRP:Single responsibility principle)
定義
一個類應該只有一個發生變化的原因,即一個類只負責一項職責。
如果一個類有多個職責,這些職責就耦合在了一起。當一個職責發生變化時,可能會影響其它的職責。另外,多個職責耦合在一起會影響復用性。
此原則的核心是解耦和增強內聚性。
由來
類A負責兩個職責:職責P1,職責P2。當由于職責P1需求發生改變而需要修改類A時,有可能會導致原本運行正常的職責P2功能發生故障。
解決方案
遵循SRP。分別建立兩個類A1、A2,使A1完成職責P1,A2完成職責P2。這樣,當修改類A1時,不會影響到職責A2;同理,當修改A2時,也不會影響到職責P1。
優點
降低類的復雜度,一個類只負責一項職責,其邏輯肯定要比負責多項職責簡單的多。
提高類的可讀性,提高系統的可維護性。
變更引起的風險降低,變更是必然的,如果SRP遵守的好,當修改一個功能時,可以顯著降低對其他功能的影響。
e.g.
iOS開發中,SRP最好的反例的應該就是 Massive View Controller。比如隨便寫一個簡單的應用程序,一般都會生成一個ViewController類,于是我們將各種各樣的代碼,算法、網絡請求、數據庫訪問等等都放在這個類里面,這就意味著,無論任何需求變化,都要來修改ViewController這個類,這其實是很糟糕的,維護麻煩、復用不可能、缺乏靈活性等。關于這點網上也有很多解決方法:8 種模式幫你告別 Massive View Controller,但無論什么方法,都是在提倡優化職責劃分,也就是SRP的思想。
曾幾何時我們很自然地將Model傳給Cell,然后讓Cell解析Model去渲染視圖,并且感覺沒有什么不妥,美其曰“Cell的封裝”。代碼如下:
TestCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TestCell"];
if (!cell) {
cell = (TestCell *)[[[NSBundle mainBundle] loadNibNamed:@"TestCell" owner:self options:nil] lastObject];
}
TestModel *model = self.dataList[indexPath.row];
[cell configWithModel:model];
殊不知這已經違背了SRP,Cell的職責是描述與渲染自身,解析Model這個職責不屬于Cell,并且在Cell中引入Model會增加不必要的依賴,Cell需要根據Model的改變而做出相應的修改,不利于Cell的復用。做過Android開發的同學知道,其實如何讓Model的數據呈現在Cell上是Adapter需要做的事情。
一些看法
用一個場景來描繪下。
用一個類描述程序員寫代碼
@implementation Programmer
-(Void) program:(NSString* name){
NSLog(@"%@寫OC代碼",name);
}
@end
//Client
Programmer* programmer = [[Programmer alloc]init];
[programmer program:@"iOS工程師"];
//Result
“iOS工程師寫OC代碼”
殊不知,iOS只是代碼界的一部分
[programmer program:@"前端工程師"];
//Result
“前端工程師寫OC代碼”
發現不對勁了,這個時候想到了SRP,要不這樣改改
@implementation IOSProgrammer
-(Void) program:(NSString* name){
NSLog(@"%@寫OC代碼",name);
}
@end
@implementation WebProgrammer
-(Void) program:(NSString* name){
NSLog(@"%@寫JS代碼",name);
}
@end
//Client
IOSProgrammer* iOSprogrammer = [[IOSProgrammer alloc]init];
[iOSprogrammer program:@"iOS工程師"];
WebProgrammer* webProgrammer = [[WebProgrammer alloc]init];
[webProgrammer program:@"前端工程師"];
//Result
“iOS工程師寫OC代碼”
“前端工程師寫JS代碼”
我們會發現如果這樣修改花銷是很大的,除了將原來的類分解之外,還需要修改客戶端。而直接修改類Programmer來達成目的雖然違背了SRP但花銷卻小的多,代碼如下:
@implementation Programmer
-(Void) program:(NSString* name){
if([name isEqualToString:@"iOS工程師"]){
NSLog(@"%@寫OC代碼",name);
}else if([name isEqualToString:@"前端工程師"]){
NSLog(@"%@寫JS代碼",name);
}
}
@end
可以看到,這種修改方式要簡單的多。但是卻存在著隱患:有一天需要后臺程序員寫PHP,則又需要修改Programmer類的program方法,而對原有代碼的修改會對調用iOS工程師、前端工程師帶來風險。這種修改方式直接在代碼級別上違背了SRP,雖然修改起來最簡單,但隱患卻是最大。
那么還有別的方式嗎?答案是肯定的,代碼如下:
@implementation Programmer
-(Void) program:(NSString* name){
NSLog(@"%@寫OC代碼",name);
}
-(Void) program2:(NSString* name){
NSLog(@"%@寫JS代碼",name);
}
@end
//Client
Programmer* programmer = [[Programmer alloc]init];
[programmer program:@"iOS工程師"];
Programmer* programmer2 = [[Programmer alloc]init];
[programmer2 program2:@"前端工程師"];
//Result
“iOS工程師寫OC代碼”
“前端工程師寫JS代碼”
這種在類中新加一個方法的修改方式,雖然也違背了SRP,但在方法級別上卻是符合SRP的,因為它并沒有動原來方法的代碼。
這三種方式各有優缺點,在開發中,需要根據實際情況來確定。需要注意的是:只有邏輯足夠簡單,才可以在代碼級別上違反SRP;只有類中方法數量足夠少,才可以在方法級別上違反SRP;
很多人對SRP不屑一顧,因為它太簡單了。但即便是經驗豐富的程序員寫出的程序,也會有違背這一原則的代碼存在。其原因是因為有職責擴散。所謂職責擴散,就是因為某種原因,職責P被分化為粒度更細的職責P1和P2。需要注意的是:在職責擴散到我們無法控制的程度之前,要立刻對代碼進行重構。