1. 何為單一職責原則
定義:就一個類而言,應該僅有一個引起它變化的原因。
這是六大原則中最簡單的一種,通俗點講,就是不存在多個原因使得一個類發生變化,一個類只負責一種職責。
優點:
類的復雜度降低,一個類只負責一個功能,其邏輯要比負責多項功能簡單的多;
類的可讀性增強,閱讀起來輕松;
可維護性強,一個易讀、簡單的類自然也容易維護;
變更引起的風險降低,變更時必然的,如果單一職責原則遵守的好,當修改一個功能時,可以顯著降低對其功能的影響;
2. 情景設置
假設有一個類C,它負責兩個不同的職責:職責P1和P2。當職責P1需求發生改變而需要修改類C時,有可能會導致原本運行正常的職責P2功能發生故障。
解決方案
遵循單一職責原則。分別建立兩個類C1、C2,使C1完成職責P1,C2完成職責P2。這樣,當修改類C1時,不會使職責P2發生故障風險;同理,當修改C2時,也不會使職責P1發生故障風險。
說到這里,大家會覺得這個原則太簡單了。稍有經驗的程序員,即使沒有聽說過單一職責原則,在設計軟件時也會自覺的遵守這一重要原則。在實際的項目開發中,誰也不希望因為修改了一個功能導致其他的功能發生故障。而避免出現這一問題的方法便是遵循單一職責原則。雖然單一職責原則如此簡單,并且被認為是常識,即便是經驗豐富的程序員寫出的程序,也會有違背這一原則的代碼存在。為什么會出現這種現象呢?因為有職責擴散。實際項目中,因為某種原因,職責P被分化為粒度更細的職責P1和P2。
比如:類C只負責一個職責P,這樣設計是符合單一職責原則的。后來由于某種原因,也許是需求變更了,也許是客戶提出了新的功能,需要將職責P細分為粒度更細的職責P1,P2,這時如果要使程序遵循單一職責原則,需要將類C也分解為兩個類C1和C2,分別負責P1、P2兩個職責。但是在程序已經寫好的情況下,這樣做有時候需要花費更多的工作量。在項目工期緊張的情況下,我們通常會簡單的修改類C,用它來負責兩個職責,雖然這樣做有悖于單一職責原則。(這樣做的風險在于職責擴散的不確定性,因為在未來可能會擴散出P1,P2,P3,P4……Pn。所以記住,在職責擴散到我們無法控制的程度之前,立刻對代碼進行重構。)
3. 代碼示例
說一個和我們密切相關的場景:員工的工資計算。剛開始的時候,我們會新建一個員工類,在員工類里面有一個計算工資的方法,代碼如下所示:
(1)Employee
@interface Employee : NSObject
// 計算工資
- (void)calculateSalary:(NSString *)name;
@end
@implementation Employee
- (void)calculateSalary:(NSString *)name
{
NSLog(@"%@的工資是100",name);
}
@end
(2)調用代碼
Employee *employee = [[Employee alloc] init];
[employee calculateSalary:@"張三"];
[employee calculateSalary:@"李四"];
產品上線后,問題出來了,因為員工的崗位不同,工資的計算是不一樣的。修改時如果遵循單一職責原則,需要將Employee類細分為總監類Director、經理類Manager、普通員工類Staff,這三個類的實現代碼和Employee類一樣,只是方法calculateSalary有所不同。
上面的修改方式是在遵循單一職責原則下進行的,從修改中,我們可以看到,這樣修改的工作量相對較大,需要新增不同的崗位類,還需要修改調用代碼。實際項目開發中,我們可能會采取如下兩種方式:
【方式一】:直接修改Employee類里面的calculateSalary方法,在這里,我們需要對calculateSalary方法增加一個參數,以標識員工的崗位。
修改后的calculateSalary方法如下所示:
// 計算工資,增加員工崗位的標識(Director:總監;Manager:經理;Staff:普通員工)
- (void)calculateSalary:(NSString *)name flag:(NSString *)flag
{
if ([flag isEqualToString:@"Director"])
{
NSLog(@"%@總監的工資是10000",name);
}
else if ([flag isEqualToString:@"Manager"])
{
NSLog(@"%@經理的工資是1000",name);
}
else if ([flag isEqualToString:@"Staff"])
{
NSLog(@"%@員工的工資是100",name);
}
}
調用代碼如下:
Employee *employee = [[Employee alloc] init];
[employee calculateSalary:@"張三" flag:@"Director"];
[employee calculateSalary:@"李四" flag:@"Manager"];
[employee calculateSalary:@"王五" flag:@"Staff"];
從上面可以看到,這種修改方式相對要簡單的多,是直接在代碼級別上違背了單一職責原則,雖然修改起來最簡單,但隱患卻也是最大的。假設有一天需要將總監分為財務總監和研發總監,則又需要修改Employee類的calculateSalary方法,而對原有代碼的修改會對已有功能帶來風險(可能會存在遺漏或者疏忽)。
【方式二】:在Employee類中新增不同崗位的工資計算方法,.h文件中新加的方法定義如下所示:
// 總監工資計算
- (void)directorCalculateSalary:(NSString *)name;
// 經理工資計算
- (void)managerCalculateSalary:(NSString *)name;
// 普通員工工資計算
- (void)staffCalculateSalary:(NSString *)name;
調用代碼如下:
Employee *employee = [[Employee alloc] init];
[employee directorCalculateSalary:@"張三"];
[employee managerCalculateSalary:@"李四"];
[employee staffCalculateSalary:@"王五"];
可以看到,方式二沒有改動原來的方法,而是在類中新加了三個方法,這樣雖然也違背了單一職責原則,但在方法級別上卻是符合單一職責原則,因為它并沒有改變原來方法的代碼。
上面三種方式各有優缺點,那么在實際編程中,該采用哪一種呢?這個問題沒有標準答案,需要根據實際情況來確定。