定義
A class should have a single responsibility, where a responsibility is nothing but a reason to change.
即:一個(gè)類只允許有一個(gè)職責(zé)蜒程,即只有一個(gè)導(dǎo)致該類變更的原因媚污。
定義的解讀
類職責(zé)的變化往往就是導(dǎo)致類變化的原因:也就是說(shuō)如果一個(gè)類具有多種職責(zé)审姓,就會(huì)有多種導(dǎo)致這個(gè)類變化的原因,從而導(dǎo)致這個(gè)類的維護(hù)變得困難届谈。
往往在軟件開發(fā)中隨著需求的不斷增加,可能會(huì)給原來(lái)的類添加一些本來(lái)不屬于它的一些職責(zé)弯汰,從而違反了單一職責(zé)原則艰山。如果我們發(fā)現(xiàn)當(dāng)前類的職責(zé)不僅僅有一個(gè),就應(yīng)該將本來(lái)不屬于該類真正的職責(zé)分離出去咏闪。
不僅僅是類程剥,函數(shù)(方法)也要遵循單一職責(zé)原則,即:一個(gè)函數(shù)(方法)只做一件事情汤踏。如果發(fā)現(xiàn)一個(gè)函數(shù)(方法)里面有不同的任務(wù)织鲸,則需要將不同的任務(wù)以另一個(gè)函數(shù)(方法)的形式分離出去。
優(yōu)點(diǎn)
如果類與方法的職責(zé)劃分得很清晰溪胶,不但可以提高代碼的可讀性搂擦,更實(shí)際性地更降低了程序出錯(cuò)的風(fēng)險(xiǎn),因?yàn)榍逦拇a會(huì)讓bug無(wú)處藏身哗脖,也有利于bug的追蹤瀑踢,也就是降低了程序的維護(hù)成本扳还。
代碼講解
單一職責(zé)原則的demo比較簡(jiǎn)單,通過(guò)對(duì)象(屬性)的設(shè)計(jì)上講解已經(jīng)足夠橱夭,不需要具體的客戶端調(diào)用氨距。我們先看一下需求點(diǎn):
需求點(diǎn)
初始需求:需要?jiǎng)?chuàng)造一個(gè)員工類,這個(gè)類有員工的一些基本信息棘劣。
新需求:增加兩個(gè)方法:
- 判定員工在今年是否升職
- 計(jì)算員工的薪水
先來(lái)看一下不好的設(shè)計(jì):
不好的設(shè)計(jì)
//================== Employee.h ==================
@interface Employee : NSObject
//============ 初始需求 ============
@property (nonatomic, copy) NSString *name; //員工姓名
@property (nonatomic, copy) NSString *address; //員工住址
@property (nonatomic, copy) NSString *employeeID; //員工ID
//============ 新需求 ============
//計(jì)算薪水
- (double)calculateSalary;
//今年是否晉升
- (BOOL)willGetPromotionThisYear;
@end
由上面的代碼可以看出:
- 在初始需求下俏让,我們創(chuàng)建了
Employee
這個(gè)員工類,并聲明了3個(gè)員工信息的屬性:?jiǎn)T工姓名茬暇,地址首昔,員工ID。 - 在新需求下糙俗,兩個(gè)方法直接加到了員工類里面勒奇。
新需求的做法看似沒(méi)有問(wèn)題,因?yàn)槎际呛蛦T工有關(guān)的巧骚,但卻違反了單一職責(zé)原則:因?yàn)檫@兩個(gè)方法并不是員工本身的職責(zé)赊颠。
-
calculateSalary
這個(gè)方法的職責(zé)是屬于會(huì)計(jì)部門的:薪水的計(jì)算是會(huì)計(jì)部門負(fù)責(zé)。 -
willPromotionThisYear
這個(gè)方法的職責(zé)是屬于人事部門的:考核與晉升機(jī)制是人事部門負(fù)責(zé)劈彪。
而上面的設(shè)計(jì)將本來(lái)不屬于員工自己的職責(zé)強(qiáng)加進(jìn)了員工類里面巨税,而這個(gè)類的設(shè)計(jì)初衷(原始職責(zé))就是單純地保留員工的一些信息而已。因此這么做就是給這個(gè)類引入了新的職責(zé)粉臊,故此設(shè)計(jì)違反了單一職責(zé)原則草添。
我們可以簡(jiǎn)單想象一下這么做的后果是什么:如果員工的晉升機(jī)制變了,或者稅收政策等影響員工工資的因素變了扼仲,我們還需要修改當(dāng)前這個(gè)類远寸。
那么怎么做才能不違反單一職責(zé)原則呢?- 我們需要將這兩個(gè)方法(責(zé)任)分離出去屠凶,讓本應(yīng)該處理這類任務(wù)的類來(lái)處理驰后。
較好的設(shè)計(jì)
我們保留員工類的基本信息:
//================== Employee.h ==================
@interface Employee : NSObject
//初始需求
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *address;
@property (nonatomic, copy) NSString *employeeID;
接著創(chuàng)建新的會(huì)計(jì)部門類:
//================== FinancialApartment.h ==================
#import "Employee.h"
//會(huì)計(jì)部門類
@interface FinancialApartment : NSObject
//計(jì)算薪水
- (double)calculateSalary:(Employee *)employee;
@end
和人事部門類:
//================== HRApartment.h ==================
#import "Employee.h"
//人事部門類
@interface HRApartment : NSObject
//今年是否晉升
- (BOOL)willGetPromotionThisYear:(Employee*)employee;
@end
通過(guò)創(chuàng)建了兩個(gè)分別專門處理薪水和晉升的部門,會(huì)計(jì)部門和人事部門的類:FinancialApartment
和 HRApartment
矗愧,把兩個(gè)任務(wù)(責(zé)任)分離了出去灶芝,讓本該處理這些職責(zé)的類來(lái)處理這些職責(zé)。
這樣一來(lái)唉韭,不僅僅在此次新需求中滿足了單一職責(zé)原則夜涕,以后如果還要增加人事部門和會(huì)計(jì)部門處理的任務(wù),就可以直接在這兩個(gè)類里面添加即可属愤。
下面來(lái)看一下這兩個(gè)設(shè)計(jì)的UML 類圖女器,可以更形象地看出兩種設(shè)計(jì)上的區(qū)別:
UML 類圖對(duì)比
未實(shí)踐單一職責(zé)原則:
實(shí)踐了單一職責(zé)原則:
可以看到,在實(shí)踐了單一職責(zé)原則的 UML 類圖中住诸,不屬于
Employee
的兩個(gè)職責(zé)被分類了FinancialApartment
類 和HRApartment
類驾胆。(在 UML 類圖中涣澡,虛線箭頭表示依賴關(guān)系,常用在方法參數(shù)等丧诺,由依賴方指向被依賴方)
上面說(shuō)過(guò)除了類要遵循單一職責(zé)設(shè)計(jì)原則之外入桂,在函數(shù)(方法)的設(shè)計(jì)上也要遵循單一職責(zé)的設(shè)計(jì)原則。因函數(shù)(方法)的單一職責(zé)原則理解起來(lái)比較容易驳阎,故在這里就不提供Demo和UML 類圖了抗愁。
可以簡(jiǎn)單舉一個(gè)例子:
APP的默認(rèn)導(dǎo)航欄的樣式是這樣的:
- 白色底
- 黑色標(biāo)題
- 底部有陰影
那么創(chuàng)建默認(rèn)導(dǎo)航欄的偽代碼可能是這樣子的:
//默認(rèn)樣式的導(dǎo)航欄
- (void)createDefaultNavigationBarWithTitle:(NSString *)title{
//create white color background view
//create black color title
//create shadow bottom
}
現(xiàn)在我們可以用這個(gè)方法統(tǒng)一創(chuàng)建默認(rèn)的導(dǎo)航欄了。 但是過(guò)不久又有新的需求來(lái)了搞隐,有的頁(yè)面的導(dǎo)航欄需要做成透明的,因此需要一個(gè)透明樣式的導(dǎo)航欄:
- 透明底
- 白色標(biāo)題
- 底部無(wú)陰影
針對(duì)這個(gè)需求远搪,我們可以新增一個(gè)方法:
//透明樣式的導(dǎo)航欄
- (void)createTransParentNavigationBarWithTitle:(NSString *)title{
//create transparent color background view
//create white color title
}
看出問(wèn)題來(lái)了么劣纲?在這兩個(gè)方法里面,創(chuàng)造background view和 title color title的方法的差別僅僅是顏色不同而已谁鳍,而其他部分的代碼是重復(fù)的癞季。 因此我們應(yīng)該將這兩個(gè)方法抽出來(lái):
//根據(jù)傳入的顏色參數(shù)設(shè)置導(dǎo)航欄的背景色
- (void)createBackgroundViewWithColor:(UIColor)color;
//根據(jù)傳入的標(biāo)題字符串和顏色參數(shù)設(shè)置標(biāo)題
- (void)createTitlewWithColorWithTitle:(NSString *)title color:(UIColor)color;
而且上面的制造陰影的部分也可以作為方法抽出來(lái):
- (void)createShadowBottom;
這樣一來(lái),原來(lái)的兩個(gè)方法可以寫成:
//默認(rèn)樣式的導(dǎo)航欄
- (void)createDefaultNavigationBarWithTitle:(NSString *)title{
//設(shè)置白色背景
[self createBackgroundViewWithColor:[UIColor whiteColor]];
//設(shè)置黑色標(biāo)題
[self createTitlewWithColorWithTitle:title color:[UIColor blackColor]];
//設(shè)置底部陰影
[self createShadowBottom];
}
//透明樣式的導(dǎo)航欄
- (void)createTransParentNavigationBarWithTitle:(NSString *)title{
//設(shè)置透明背景
[self createBackgroundViewWithColor:[UIColor clearColor]];
//設(shè)置白色標(biāo)題
[self createTitlewWithColorWithTitle:title color:[UIColor whiteColor]];
}
而且我們也可以將里面的方法拿出來(lái)在外面調(diào)用也可以:
設(shè)置默認(rèn)樣式的導(dǎo)航欄:
//設(shè)置白色背景
[navigationBar createBackgroundViewWithColor:[UIColor whiteColor]];
//設(shè)置黑色標(biāo)題
[navigationBar createTitlewWithColorWithTitle:title color:[UIColor blackColor]];
//設(shè)置陰影
[navigationBar createShadowBottom];
設(shè)置透明樣式的導(dǎo)航欄:
//設(shè)置透明色背景
[navigationBar createBackgroundViewWithColor:[UIColor clearColor]];
//設(shè)置白色標(biāo)題
[navigationBar createTitlewWithColorWithTitle:title color:[UIColor whiteColor]];
這樣一來(lái)倘潜,無(wú)論寫在一個(gè)大方法里面調(diào)用或是分別在外面調(diào)用绷柒,都能很清楚地看到導(dǎo)航欄的每個(gè)元素是如何生成的,因?yàn)槊總€(gè)職責(zé)都分配到了一個(gè)單獨(dú)的方法里面涮因。而且還有一個(gè)好處是废睦,透明導(dǎo)航欄如果遇到淺色背景的話,使用白色字體不如使用黑色字體好养泡,所以遇到這種情況我們可以在createTitlewWithColorWithTitle:color:
方法里面?zhèn)魅牒谏怠?而且今后可能還會(huì)有更多的導(dǎo)航欄樣式嗜湃,那么我們只需要分別改變傳入的色值即可,不需要有大量的重復(fù)代碼了澜掩,修改起來(lái)也很方便购披。
如何實(shí)踐
對(duì)于上面的員工類的例子,或許是因?yàn)槲覀兿热霝橹骷玳牛酪粋€(gè)公司的合理組織架構(gòu)刚陡,覺得這么設(shè)計(jì)理所當(dāng)然。但是在實(shí)際開發(fā)中株汉,我們很容易會(huì)將不同的責(zé)任揉在一起筐乳,這點(diǎn)還是需要開發(fā)者注意的。