代碼的 6 大設計原則-單一職責原則

出自書《設計模式之蟬》

單一職責原則的英文名稱是Single Responsibility Principle,簡稱是SRP圣拄。這個設計原則備受爭議荣刑,只要你想和別人爭執(zhí)、慪氣或者是吵架与柑,這個則是屢試不爽的谤辜。如果你是老大,看到一個接口或類是這樣或那樣設計的价捧,你就問一句:“你設計的類符合SRP原則嗎丑念?”保準對方立馬“萎縮”掉,而且還一臉崇拜地看著你结蟋,心想:“老大確實英明”脯倚。這個原則存在爭議之處在哪里呢?就是對職責的定義嵌屎,什么是類的職責推正,以及怎么劃分類的職責

我們先舉個例子來說明什么是單一職責原則宝惰。

只要做過項目植榕,肯定要接觸到用戶、機構尼夺、角色管理這些模塊尊残,基本上使用的都是RBAC模型(Role-Based Access Control,基于角色的訪問控制淤堵,通過分配和取消角色來完成用戶權限的授予和取消寝衫,使動作主體(用戶)與資源的行為(權限)分離),確實是一個很好的解決辦法拐邪。我們這里要講的是用戶管理慰毅、修改用戶的信息、增加機構(一個人屬于多個機構)扎阶、增加角色等事富,用戶有這么多的信息和行為要維護,我們就把這些寫到一個接口中乘陪,都是用戶管理類嘛统台,我們先來看它的類圖,如圖:

用戶信息維護類圖

太Easy的類圖了啡邑,我相信贱勃,即使是一個初級的程序員也可以看出這個接口設計得有問題,用戶的屬性和用戶的行為沒有分開谤逼,這是一個嚴重的錯誤贵扰!這個接口確實設計得一團糟,應該把用戶的信息抽取成一個BO(Business Object流部,業(yè)務對象)戚绕,把行為抽取成一個Biz(Business Logic,業(yè)務邏輯)枝冀,按照這個思路對類圖進行修正舞丛,如圖:

職責劃分后

重新拆封成兩個接口耘子,IUserBO負責用戶的屬性,簡單地說球切,IUserBO的職責就是收集和反饋用戶的屬性信息谷誓;IUserBiz負責用戶的行為,完成用戶信息的維護和變更吨凑。各位可能要說了捍歪,這個與我實際工作中用到的User類還是有差別的呀!別著急鸵钝,我們先來看一看分拆成兩個接口怎么使用糙臼。OK,我們現在是面向接口編程恩商,所以產生了這個UserInfo對象之后弓摘,當然可以把它當IUserBO接口使用。也可以當IUserBiz接口使用痕届,這要看你在什么地方使用了韧献。要獲得用戶信息,就當是IUserBO的實現類研叫;要是希望維護用戶的信息锤窑,就把它當作IUserBiz的實現類就成了,如代碼所示:

......

IUserInfo userInfo = new UserInfo();//我要賦值了嚷炉,我就認為它是一個純粹的BO

IUserBO userBO = (IUserBO)userInfo;userBO.setPassword("abc");//我要執(zhí)行動作了渊啰,我就認為是一個業(yè)務邏輯類

IUserBiz userBiz = (IUserBiz)userInfo;userBiz.deleteUser();

......

確實可以如此,問題也解決了申屹,但是我們來分析一下剛才的動作绘证,為什么要把一個接口拆分成兩個呢?其實哗讥,在實際的使用中嚷那,我們更傾向于使用兩個不同的類或接口:一個是IUserBO,一個是IUserBiz杆煞,類圖1所示:


類圖1

以上我們把一個接口拆分成兩個接口的動作魏宽,就是依賴了單一職責原則,那什么是單一職責原則呢决乎?

單一職責原則的定義是:應該有且僅有一個原因引起類的變更队询。


解釋到這里,估計你已經很不屑了构诚,“切蚌斩!這么簡單的東西還要講?范嘱!”好送膳,我們來講點復雜的员魏。SRP的原話解釋是:

There should never be more than one reason for a class to change.

這句話初中生都能看懂,不多說肠缨,但是看懂是一碼事逆趋,實施就是另外一碼事了盏阶。上面講的例子很好理解晒奕,在實際項目中大家都已經這么做了,那我們再來看看下面這個例子是否好理解名斟。電話這玩意脑慧,是現代人都離不了,電話通話的時候有4個過程發(fā)生:撥號砰盐、通話闷袒、回應、掛機岩梳,那我們寫一個接口囊骤,其類圖2所示。

電話類圖

我不是有意要冒犯IPhone的冀值,同名純屬巧合也物,我們來看一個這個過程的代碼:

public interface IPhone {

????//撥通電話

????public void dial(String phoneNumber);

????//通話

????public void chat(Object o);

????//通話完畢,掛電話

????public void hangup();

}

實現類也比較簡單列疗,我就不再寫了滑蚯,大家看看這個接口有沒有問題?我相信大部分的讀者都會說這個沒有問題呀抵栈,以前我就是這么做的呀告材,某某書上也是這么寫的呀,還有什么什么的源碼也是這么寫的古劲!是的斥赋,這個接口接近于完美,看清楚了产艾,是“接近”灿渴!單一職責原則要求一個接口或類只有一個原因引起變化,也就是一個接口或類只有一個職責胰舆,它就負責一件事情骚露,看看上面的接口只負責一件事情嗎?是只有一個原因引起變化嗎缚窿?好像不是棘幸!IPhone這個接口可不是只有一個職責,它包含了兩個職責:一個是協(xié)議管理倦零,一個是數據傳送误续。dial()和hangup()兩個方法實現的是協(xié)議管理吨悍,分別負責撥號接通和掛機;chat()實現的是數據的傳送蹋嵌,把我們說的話轉換成模擬信號或數字信號傳遞到對方育瓜,然后再把對方傳遞過來的信號還原成我們聽得懂的語言。我們可以這樣考慮這個問題栽烂,協(xié)議接通的變化會引起這個接口或實現類的變化嗎躏仇?會的!那數據傳送(想想看腺办,電話不僅僅可以通話焰手,還可以上網)的變化會引起這個接口或實現類的變化嗎?會的怀喉!那就很簡單了书妻,這里有兩個原因都引起了類的變化。這兩個職責會相互影響嗎躬拢?電話撥號躲履,我只要能接通就成,甭管是電信的還是網通的協(xié)議聊闯;電話連接后還關心傳遞的是什么數據嗎工猜?通過這樣的分析,我們發(fā)現類圖上的IPhone接口包含了兩個職責馅袁,而且這兩個職責的變化不相互影響域慷,那就考慮拆分成兩個接口,其下圖所示:

職責分明的電話類圖

這個類圖看上去有點復雜了汗销,完全滿足了單一職責原則的要求犹褒,每個接口職責分明,結構清晰弛针,但是我相信你在設計的時候肯定不會采用這種方式叠骑,一個手機類要把ConnectionManager和DataTransfer組合在一塊才能使用。組合是一種強耦合關系削茁,你和我都有共同的生命期宙枷,這樣的強耦合關系還不如使用接口實現的方式呢,而且還增加了類的復雜性茧跋,多了兩個類慰丛。經過這樣的思考后,我們再修改一下類圖瘾杭,如下圖:


簡潔清晰诅病、職責分明的電話類圖

這樣的設計才是完美的,一個類實現了兩個接口,把兩個職責融合在一個類中贤笆。你會覺得這個Phone有兩個原因引起變化了呀蝇棉,是的,但是別忘記了我們是面向接口編程芥永,我們對外公布的是接口而不是實現類篡殷。而且,如果真要實現類的單一職責埋涧,這個就必須使用上面的組合模式了板辽,這會引起類間耦合過重、類的數量增加等問題飞袋,人為地增加了設計的復雜性戳气。通過上面的例子链患,我們來總結一下單一職責原則有什么好處:

● 類的復雜性降低巧鸭,實現什么職責都有清晰明確的定義;

● 可讀性提高麻捻,復雜性降低纲仍,那當然可讀性提高了;

● 可維護性提高贸毕,可讀性提高,那當然更容易維護了;

● 變更引起的風險降低潦闲,變更是必不可少的牵啦,如果接口的單一職責做得好,一個接口修改只對相應的實現類有影響摊腋,對其他的接口無影響沸版,這對系統(tǒng)的擴展性、維護性都有非常大的幫助兴蒸。

看過電話這個例子后视粮,是不是想反思一下了,我以前的設計是不是有點問題了橙凳?不蕾殴,不是的,不要懷疑自己的技術能力岛啸,單一職責原則最難劃分的就是職責钓觉。一個職責一個接口,但問題是“職責”沒有一個量化的標準坚踩,一個類到底要負責那些職責荡灾?這些職責該怎么細化?細化后是否都要有一個接口或類?這些都需要從實際的項目去考慮卧晓,從功能上來說芬首,定義一個IPhone接口也沒有錯,實現了電話的功能逼裆,而且設計還很簡單郁稍,僅僅一個接口一個實現類,實際的項目我想大家都會這么設計胜宇。項目要考慮可變因素和不可變因素耀怜,以及相關的收益成本比率,因此設計一個IPhone接口也可能是沒有錯的桐愉。但是财破,如果純從“學究”理論上分析就有問題了,有兩個可以變化的原因放到了一個接口中从诲,這就為以后的變化帶來了風險左痢。如果以后模擬電話升級到數字電話,我們提供的接口IPhone是不是要修改了系洛?接口修改對其他的Invoker類是不是有很大影響俊性?

注意 !C璩丁定页!

單一職責原則提出了一個編寫程序的標準,用“職責”或“變化原因”來衡量接口或類設計得是否優(yōu)良绽诚,但是“職責”和“變化原因”都是不可度量的典徊,因項目而異,因環(huán)境而異恩够。

對于單一職責原則卒落,我的建議是接口一定要做到單一職責,類的設計盡量做到只有一個原因引起變化玫鸟。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末导绷,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子屎飘,更是在濱河造成了極大的恐慌妥曲,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钦购,死亡現場離奇詭異檐盟,居然都是意外死亡,警方通過查閱死者的電腦和手機押桃,發(fā)現死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門葵萎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事羡忘』蚜。” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵卷雕,是天一觀的道長节猿。 經常有香客問我,道長漫雕,這世上最難降的妖魔是什么滨嘱? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮浸间,結果婚禮上太雨,老公的妹妹穿的比我還像新娘。我一直安慰自己魁蒜,他們只是感情好囊扳,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著梅惯,像睡著了一般宪拥。 火紅的嫁衣襯著肌膚如雪仿野。 梳的紋絲不亂的頭發(fā)上铣减,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音脚作,去河邊找鬼葫哗。 笑死,一個胖子當著我的面吹牛球涛,可吹牛的內容都是我干的劣针。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼亿扁,長吁一口氣:“原來是場噩夢啊……” “哼捺典!你這毒婦竟也來了?” 一聲冷哼從身側響起从祝,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤襟己,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后牍陌,有當地人在樹林里發(fā)現了一具尸體擎浴,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年毒涧,在試婚紗的時候發(fā)現自己被綠了贮预。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖仿吞,靈堂內的尸體忽然破棺而出滑频,到底是詐尸還是另有隱情,我是刑警寧澤唤冈,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布误趴,位于F島的核電站,受9級特大地震影響务傲,放射性物質發(fā)生泄漏凉当。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一售葡、第九天 我趴在偏房一處隱蔽的房頂上張望看杭。 院中可真熱鬧,春花似錦挟伙、人聲如沸楼雹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贮缅。三九已至,卻和暖如春介却,著一層夾襖步出監(jiān)牢的瞬間谴供,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工齿坷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留桂肌,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓永淌,卻偏偏與公主長得像崎场,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子遂蛀,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容

  • 目錄: 設計模式六大原則(1):單一職責原則 設計模式六大原則(2):里氏替換原則 設計模式六大原則(3):依賴倒...
    加油小杜閱讀 728評論 0 1
  • 程序設計的6大原則: 單一職責原則里氏替換原則依賴倒置原則接口隔離原則迪米特法則開閉原則 從根本學好谭跨,理解為什么要...
    silencefun閱讀 2,408評論 1 4
  • 設計模式6大原則 轉自:http://www.cnblogs.com/devinzhang/archive/201...
    犀利的小眼神閱讀 432評論 0 1
  • 單一職責原則 (SRP) 全稱 SRP , Single Responsibility Principle 單一職...
    米莉_L閱讀 1,765評論 2 5
  • console 不只能用來調試 還能做很多有意思的things 直接上代碼
    zz77zz閱讀 361評論 0 1