目錄
單一職責原則(SRP:Single responsibility principle)
定義
一個類應該只有一個發(fā)生變化的原因,即一個類只負責一項職責。
如果一個類有多個職責,這些職責就耦合在了一起还惠。當一個職責發(fā)生變化時,可能會影響其它的職責蚕键。另外互拾,多個職責耦合在一起會影響復用性。
此原則的核心是解耦和增強內聚性寄猩。
由來
類A負責兩個職責:職責P1嫉晶,職責P2。當由于職責P1需求發(fā)生改變而需要修改類A時,有可能會導致原本運行正常的職責P2功能發(fā)生故障替废。
解決方案
遵循SRP箍铭。分別建立兩個類A1、A2椎镣,使A1完成職責P1诈火,A2完成職責P2。這樣状答,當修改類A1時冷守,不會影響到職責A2;同理惊科,當修改A2時拍摇,也不會影響到職責P1。
優(yōu)點
降低類的復雜度馆截,一個類只負責一項職責充活,其邏輯肯定要比負責多項職責簡單的多。
提高類的可讀性蜡娶,提高系統(tǒng)的可維護性混卵。
變更引起的風險降低,變更是必然的窖张,如果SRP遵守的好幕随,當修改一個功能時,可以顯著降低對其他功能的影響荤堪。
e.g.
iOS開發(fā)中合陵,SRP最好的反例的應該就是 Massive View Controller。比如隨便寫一個簡單的應用程序澄阳,一般都會生成一個ViewController類拥知,于是我們將各種各樣的代碼,算法碎赢、網絡請求低剔、數(shù)據庫訪問等等都放在這個類里面,這就意味著肮塞,無論任何需求變化襟齿,都要來修改ViewController這個類,這其實是很糟糕的枕赵,維護麻煩猜欺、復用不可能、缺乏靈活性等拷窜。關于這點網上也有很多解決方法:8 種模式幫你告別 Massive View Controller开皿,但無論什么方法涧黄,都是在提倡優(yōu)化職責劃分,也就是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開發(fā)的同學知道嫁赏,其實如何讓Model的數(shù)據呈現(xiàn)在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代碼”
發(fā)現(xiàn)不對勁了,這個時候想到了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代碼”
我們會發(fā)現(xiàn)如果這樣修改花銷是很大的攘乒,除了將原來的類分解之外,還需要修改客戶端惋鹅。而直接修改類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的憔涉,因為它并沒有動原來方法的代碼订框。
這三種方式各有優(yōu)缺點,在開發(fā)中兜叨,需要根據實際情況來確定布蔗。需要注意的是:只有邏輯足夠簡單藤违,才可以在代碼級別上違反SRP;只有類中方法數(shù)量足夠少纵揍,才可以在方法級別上違反SRP;
很多人對SRP不屑一顧议街,因為它太簡單了泽谨。但即便是經驗豐富的程序員寫出的程序,也會有違背這一原則的代碼存在特漩。其原因是因為有職責擴散吧雹。所謂職責擴散,就是因為某種原因涂身,職責P被分化為粒度更細的職責P1和P2雄卷。需要注意的是:在職責擴散到我們無法控制的程度之前,要立刻對代碼進行重構蛤售。