前言 面向?qū)ο蟮腟OLID設(shè)計(jì)原則,外加一個(gè)迪米特法則,就是我們常說(shuō)的5+1設(shè)計(jì)原則。↑ 五個(gè),再加一個(gè),就是5+1個(gè)。哈哈哈。這六個(gè)設(shè)計(jì)原則的位置有點(diǎn)不上不下。論原則性和理論指導(dǎo)意義,它們不如封裝繼承抽象或者高內(nèi)聚低耦合,所以在寫(xiě)代碼或者code review的時(shí)候,它們很難成為“應(yīng)該這樣做”或者“不應(yīng)該這樣做”的一個(gè)有說(shuō)服力的理由。論靈活性和實(shí)踐操作指南,它們又不如設(shè)計(jì)模式或者架構(gòu)模式,所以即使你能說(shuō)出來(lái)某段代碼違反了某項(xiàng)原則,常常也很難明確指出錯(cuò)在哪兒、要怎么改。所以,這里來(lái)討論討論這六條設(shè)計(jì)原則的“為什么”和“怎么做”。順帶,作為面向?qū)ο笤O(shè)計(jì)思想的一環(huán),這里也想聊聊它們與抽象、高內(nèi)聚低耦合、封裝繼承多態(tài)之間的關(guān)系。==================================================================單一職責(zé)原則(Single Responsibility Principle)單一職責(zé)原則非常好理解:一個(gè)類(lèi)應(yīng)當(dāng)只承擔(dān)一種職責(zé)。因?yàn)橹怀袚?dān)一種職責(zé),所以,一個(gè)類(lèi)應(yīng)該只有一個(gè)發(fā)生變化的原因。↑ 通俗一點(diǎn)來(lái)說(shuō),就是一心不要二用 ==================================================================嚴(yán)格來(lái)說(shuō),單一職責(zé)原則并沒(méi)有解決問(wèn)題,而只是把問(wèn)題從“怎樣定義類(lèi)”變成了“怎樣劃分職責(zé)”。可是,劃分職責(zé)這件事情,本身沒(méi)有一個(gè)放之四海而皆準(zhǔn)的標(biāo)準(zhǔn)。我們固然可以說(shuō)“查詢(xún)用戶(hù)銀行卡綁定狀態(tài)”、“查詢(xún)用戶(hù)實(shí)名認(rèn)證狀態(tài)”、“查詢(xún)用戶(hù)授權(quán)狀態(tài)”、“查詢(xún)用戶(hù)密碼強(qiáng)度狀態(tài)”等是不同的職責(zé),但是也可以認(rèn)為它們同屬于“查詢(xún)用戶(hù)賬號(hào)狀態(tài)”這一個(gè)職責(zé)。同時(shí),“查詢(xún)用戶(hù)銀行卡綁定狀態(tài)”這個(gè)職責(zé),也還可以拆分為“查詢(xún)用戶(hù)名下是否有銀行卡”、“判斷系統(tǒng)是否支持該銀行卡所屬銀行”、“判斷該銀行卡是否用戶(hù)本人的銀行卡”、“判斷該銀行卡是否通過(guò)四要素鑒權(quán)”等幾項(xiàng)職責(zé)。這幾種劃分方式都有各自的道理,很難說(shuō)誰(shuí)對(duì)誰(shuí)錯(cuò)。想要在這樣的基礎(chǔ)上落實(shí)單一職責(zé)原則,就真是一個(gè)將軍一個(gè)令,必然要陷入無(wú)所適從的田地。這是單一職責(zé)原則變得“食之無(wú)味、棄之有肉”的主要原因。那么,我們是不是就可以把單一職責(zé)原則丟到九霄云外呢?答案顯然是否定的。軟件開(kāi)發(fā)有個(gè)“一生之?dāng)场?,就是?fù)雜度。我們?yōu)榱税褧r(shí)間復(fù)雜度由O(n)改進(jìn)為O(log2n)而拼命改進(jìn)算法,為了把函數(shù)的圈度復(fù)雜度降到7以下而不斷重構(gòu)函數(shù),為了把協(xié)議的復(fù)雜度“分而治之”而把完整的協(xié)議分成七層或者四層,為了把復(fù)雜度拆分到不同的系統(tǒng)中而構(gòu)建微服務(wù)。但是,從系統(tǒng)的層面來(lái)考慮的話(huà),幾乎所有方式都沒(méi)有真正降低復(fù)雜度,而只是把一種復(fù)雜度轉(zhuǎn)變成另一種復(fù)雜度。改進(jìn)算法可以降低程序運(yùn)行的復(fù)雜度,但是會(huì)增加人們?nèi)ダ斫夂途S護(hù)的復(fù)雜度;對(duì)函數(shù)的重構(gòu)往往只是把復(fù)雜度從這個(gè)函數(shù)分散到其它的函數(shù)里去;分層是把整個(gè)協(xié)議的復(fù)雜度拆分到各個(gè)層次上去;微服務(wù)架構(gòu)則是把單體系統(tǒng)的復(fù)雜度拆分到不同的微服務(wù)上去。復(fù)雜度就像能量一樣,只能轉(zhuǎn)化或轉(zhuǎn)移,但是無(wú)法徹底湮滅。我們甚至可以說(shuō),除非刪代碼,否則一個(gè)系統(tǒng)的復(fù)雜度永遠(yuǎn)是只增不減的。↑ 復(fù)雜度只增不減,大概可以算是熵增定律在軟件行業(yè)的體現(xiàn)吧。 話(huà)雖如此,但我們不用感到灰心喪氣。雖然整個(gè)系統(tǒng)的復(fù)雜度居高不下,但是冰凍三尺非一日之寒,絕大多數(shù)時(shí)候,這么高的復(fù)雜度都是一點(diǎn)一點(diǎn)累加上去的。換句話(huà)說(shuō),我們絕大多數(shù)時(shí)候需要面對(duì)和處理的復(fù)雜度,往往都只是滄海一粟。所以,面對(duì)系統(tǒng)復(fù)雜度這頭巨獸,我們雖然無(wú)法一口鯨吞,但還是可以找到辦法把它逐步蠶食下去的。什么辦法呢?我認(rèn)為,最簡(jiǎn)單、最通用的辦法,就是把“邏輯復(fù)雜度”轉(zhuǎn)變?yōu)椤敖Y(jié)構(gòu)復(fù)雜度”。邏輯復(fù)雜度很好理解,一個(gè)邏輯單元(如函數(shù)、對(duì)象、模塊、甚至服務(wù)等)中的分支越多,其邏輯復(fù)雜度就越高。圈度復(fù)雜度就是邏輯復(fù)雜度的一種常用的度量方式。結(jié)構(gòu)復(fù)雜度略費(fèi)解一些,邏輯單元之間的關(guān)系(如函數(shù)調(diào)用、對(duì)象扇入/扇出、模塊引用、服務(wù)依賴(lài)等)越復(fù)雜,其結(jié)構(gòu)復(fù)雜度就越高。如果把這張關(guān)系網(wǎng)畫(huà)成圖,那么我們就可以很直觀(guān)地度量結(jié)構(gòu)復(fù)雜度了。把“邏輯復(fù)雜度”轉(zhuǎn)變?yōu)椤敖Y(jié)構(gòu)復(fù)雜度”,就是降低邏輯單元內(nèi)的邏輯復(fù)雜度、提高邏輯單元間的結(jié)構(gòu)復(fù)雜度?;蛘吆?jiǎn)單來(lái)說(shuō),就是把系統(tǒng)由“復(fù)雜邏輯、簡(jiǎn)單結(jié)構(gòu)”轉(zhuǎn)變成“簡(jiǎn)單邏輯、復(fù)雜結(jié)構(gòu)”。除了優(yōu)化算法之外,重構(gòu)函數(shù)、系統(tǒng)分層、拆分服務(wù)等方式,本質(zhì)上都是在按這種思路來(lái)應(yīng)對(duì)系統(tǒng)復(fù)雜度。↑ 手機(jī)和武器都有“模塊化”的設(shè)計(jì),也是一種“邏輯簡(jiǎn)單、結(jié)構(gòu)復(fù)雜”思路。“簡(jiǎn)單邏輯、復(fù)雜結(jié)構(gòu)”這個(gè)思路,同樣也是劃分職責(zé)的一個(gè)思路。邏輯單元盡可能的簡(jiǎn)單,也就是它所包含的職責(zé)應(yīng)當(dāng)盡可能的少。當(dāng)需要某種更復(fù)雜的邏輯時(shí),通過(guò)某種結(jié)構(gòu)把這些簡(jiǎn)單的單元組合在一起來(lái)實(shí)現(xiàn)就可以了。而這,就是我們?yōu)槭裁匆袷貑我宦氊?zé)原則的主要原因。==================================================================要說(shuō)怎樣落實(shí)單一職責(zé)原則,其實(shí)很簡(jiǎn)單:把你能拆出來(lái)的功能、職責(zé)都拆分到獨(dú)立的類(lèi)里面去。但是顯然的,這樣做會(huì)迅速地引發(fā)類(lèi)爆炸,并且導(dǎo)致開(kāi)發(fā)工作量指數(shù)級(jí)別上升——Java代碼的啰嗦麻煩是有目共睹甚至千夫所指的問(wèn)題。寫(xiě)一個(gè)類(lèi)就夠累的了,真要發(fā)生類(lèi)爆炸了,恐怕手指都要寫(xiě)抽筋了。↑ 關(guān)愛(ài)程序員,從預(yù)防腱鞘炎開(kāi)始。
又要遵守單一職責(zé)原則,又要避免開(kāi)發(fā)量過(guò)大,這簡(jiǎn)直是又要馬兒跑、又要馬兒不吃草。所以,一般我們都會(huì)在這二者之間做折衷處理。怎么折衷呢?我們可以把多種職責(zé)放到同一個(gè)類(lèi)里面;但是至少應(yīng)該把不同職責(zé)劃分到不同的方法中去。然后,在有時(shí)間或者有必要的時(shí)候,再通過(guò)重構(gòu),把它們放到單獨(dú)的類(lèi)里去。說(shuō)到重構(gòu)我忍不住多說(shuō)一嘴。我們常說(shuō)系統(tǒng)架構(gòu)是演化出來(lái)的。但是具體要怎樣演化呢?其實(shí)就是“靠重構(gòu)”。偶爾也會(huì)有人問(wèn)我“整天做CRUD,要怎么樣才能提高自己”,我的答案也是“靠重構(gòu)”。重構(gòu)是一件不僅能提升系統(tǒng)代碼質(zhì)量、還能提高個(gè)人技術(shù)能力的事情,我們應(yīng)該把它作為開(kāi)發(fā)組的一項(xiàng)日常工作來(lái)定期/不定期的推進(jìn)落地。盡管在實(shí)際工作中,由于需求任務(wù)太多、開(kāi)發(fā)資源太少、測(cè)試運(yùn)維不好配合等原因,我們很難安排專(zhuān)門(mén)的重構(gòu)任務(wù),但是,在業(yè)務(wù)需求開(kāi)發(fā)工作中加入一些重構(gòu)任務(wù),“積小勝為大勝”,也能夠起到可觀(guān)的效果。說(shuō)回單一職責(zé)原則。類(lèi)爆炸以及增加工作量的問(wèn)題,很多時(shí)候都會(huì)具體的表現(xiàn)為“要不要為這些拆分出來(lái)的類(lèi)創(chuàng)建獨(dú)立的接口”?以及,“如果要?jiǎng)?chuàng)建接口,難道要給每個(gè)獨(dú)立的類(lèi)都創(chuàng)建一個(gè)接口嗎”?很多人跟我聊過(guò)他們?yōu)槭裁床辉敢饷嫦蚪涌诰幊獭4蠖鄶?shù)人的理由都是“反正一個(gè)接口只有一個(gè)實(shí)現(xiàn)類(lèi),再聲明一個(gè)接口純屬浪費(fèi)”。在我看來(lái),這個(gè)理由跟“反正我只會(huì)把飛機(jī)當(dāng)汽車(chē)來(lái)開(kāi),所以給它裝上翅膀純屬浪費(fèi)”沒(méi)什么區(qū)別。↑ 有人想拿汽車(chē)當(dāng)飛機(jī)開(kāi);有人總拿飛機(jī)當(dāng)汽車(chē)開(kāi)。 它(抽象)也是分層次的。越是高層級(jí)的抽象,其中的細(xì)節(jié)就越少,對(duì)業(yè)務(wù)的概括能力就越高,可維護(hù)和可擴(kuò)展性也就越好;越是低層級(jí)的抽象就越“反其道而行之”:細(xì)節(jié)信息就越多、業(yè)務(wù)概括能力越低、可維護(hù)和可擴(kuò)展性也就越差。這也是為什么我們鼓勵(lì)“面向接口編程”的一個(gè)原因。一般來(lái)說(shuō),接口都是頂層抽象。在它的基礎(chǔ)上編程,維護(hù)和擴(kuò)展所受到的限制也就越少。 所以,如果你問(wèn)我“要不要為這些拆分出來(lái)的類(lèi)創(chuàng)建獨(dú)立的接口”,我的答案一定是“要”。但是,“要給每個(gè)獨(dú)立的類(lèi)都創(chuàng)建一個(gè)接口”嗎?不一定。一個(gè)接口代表一個(gè)業(yè)務(wù)抽象;如果好幾個(gè)類(lèi)都從屬于同一個(gè)業(yè)務(wù)抽象,那么它們就應(yīng)該實(shí)現(xiàn)同一個(gè)接口;否則,我們就有必要為它們創(chuàng)建不同的接口。在這里,問(wèn)題又被轉(zhuǎn)移了:我們要考慮的不再是“要不要為這個(gè)類(lèi)創(chuàng)建一個(gè)接口”,而是“這個(gè)類(lèi)承擔(dān)的職責(zé)應(yīng)該從屬于一個(gè)怎樣的抽象、或者從屬于哪個(gè)抽象”。實(shí)際上,相比于類(lèi)爆炸或者增加開(kāi)發(fā)工作量,這個(gè)新問(wèn)題才是單一職責(zé)原則帶來(lái)的最大的挑戰(zhàn)。=================================↓單一職責(zé)與面向?qū)ο蟆?/section>=================================單一職責(zé)原則非常好的體現(xiàn)了面向?qū)ο笈c管理思想之間的相似性。當(dāng)團(tuán)隊(duì)規(guī)模小、業(yè)務(wù)并不復(fù)雜的時(shí)候,我們可以讓一個(gè)人身兼數(shù)職,以此來(lái)節(jié)約成本、提高效率。隨著開(kāi)發(fā)團(tuán)隊(duì)由小變大、負(fù)責(zé)的系統(tǒng)由少變多,團(tuán)隊(duì)成員們會(huì)逐漸由全棧轉(zhuǎn)向客戶(hù)端、服務(wù)端、運(yùn)維、DBA等更專(zhuān)精的方向。隨著公司由小變大、市場(chǎng)份額和業(yè)務(wù)范圍不斷擴(kuò)張,老板們也會(huì)逐步由一人獨(dú)掌財(cái)務(wù)、人事、市場(chǎng)大權(quán)轉(zhuǎn)為把專(zhuān)權(quán)交給專(zhuān)業(yè)的“O”們。這就是管理上的“單一職責(zé)”,有時(shí)候也叫“讓專(zhuān)業(yè)的人做專(zhuān)業(yè)的事”。如果在規(guī)模大增之后不能“一事一權(quán)”、還試圖沿用以前的“事隨人定”,那很容易像武漢紅十字會(huì)那樣把事情和自己一起搞砸。面向?qū)ο笃鋵?shí)也是一樣。業(yè)務(wù)復(fù)雜度低、系統(tǒng)規(guī)模小的時(shí)候,讓一個(gè)模塊或者一個(gè)類(lèi)承擔(dān)若干種無(wú)關(guān)邏輯,還算不上什么大問(wèn)題。但是,當(dāng)邏輯越來(lái)越復(fù)雜、系統(tǒng)越來(lái)越大、跨系統(tǒng)交互越來(lái)越多的時(shí)候,如果不能把業(yè)務(wù)職責(zé)分開(kāi)、把糾纏在一起的代碼分開(kāi),而還是像剛開(kāi)始那樣把它們像麻花一樣擰在一起的話(huà),當(dāng)需要修改這些代碼中的bug時(shí)、當(dāng)需要向其中添加一些業(yè)務(wù)邏輯時(shí)、當(dāng)跑出一些“奇怪”的數(shù)據(jù)時(shí)、當(dāng)評(píng)估與之相關(guān)的需求改動(dòng)范圍和工作量時(shí),想要從這種代碼中找出我們想要的結(jié)果,真真是“難于上青天”。我曾經(jīng)重構(gòu)過(guò)一個(gè)類(lèi)似的系統(tǒng)。重構(gòu)前,光是梳理原有邏輯就花了將近三個(gè)月。重構(gòu)的核心方向就是單一職責(zé)原則和“邏輯簡(jiǎn)單、結(jié)構(gòu)復(fù)雜”:把不同的功能拆分到不同的類(lèi)中,然后用一套雖然有點(diǎn)復(fù)雜、但還算比較清晰的類(lèi)結(jié)構(gòu)把它們組織成完整的業(yè)務(wù)邏輯。相比之下,后續(xù)的改造開(kāi)發(fā)就簡(jiǎn)單得多了。↑ 重構(gòu)后的模塊劃分;更細(xì)節(jié)的我就不貼了。單一職責(zé)的思路是一樣的。 后來(lái)在公司內(nèi)部分享這次重構(gòu)的經(jīng)驗(yàn)時(shí),我打過(guò)這樣一個(gè)比方:如果你面對(duì)的是像下圖一樣雜亂無(wú)章且紛繁復(fù)雜的網(wǎng)線(xiàn),那么,即使發(fā)現(xiàn)了有個(gè)地方接觸不良,你也不知道還有哪些地方受了影響;如果你要往里面加一條、減一條、或者替換一條網(wǎng)線(xiàn),光是搞清楚這根網(wǎng)線(xiàn)連著哪些機(jī)器就夠折騰三四天了。↑ 感受感受什么叫“一團(tuán)亂麻”。 但是,如果你面對(duì)是下圖這樣的線(xiàn)纜呢?上面那些讓人頭疼的問(wèn)題,是不是都迎刃而解甚至易如反掌了呢?↑ 再感受感受什么叫“井井有條”。
這就是團(tuán)隊(duì)管理和面向?qū)ο笤O(shè)計(jì)中的單一職責(zé)原則。它是一個(gè)“大”方向,有時(shí)只有“大”團(tuán)隊(duì)、“大”系統(tǒng)需要考慮。同時(shí),它又能在最“小”處落腳:它所考慮的,往往都是“小小”的一個(gè)人、一個(gè)類(lèi),一項(xiàng)職責(zé)。=================================↑單一職責(zé)與面向?qū)ο?/span>↑ =================================大多數(shù)情況下,我們都認(rèn)為“抽象”約等于“接口”,所以,在設(shè)計(jì)抽象時(shí),單一職責(zé)原則就會(huì)搖身一變,以“接口隔離原則”的形式出現(xiàn)。盡管如此,單一職責(zé)原則與抽象設(shè)計(jì)也并非全無(wú)關(guān)聯(lián);只不過(guò),它主要關(guān)注我們?nèi)绾螌?shí)現(xiàn)一個(gè)抽象。首先,單一職責(zé)原則能夠幫助我們對(duì)抽象做進(jìn)一步的拆分,從而達(dá)到逐步分解業(yè)務(wù)復(fù)雜度的目標(biāo)。考慮到“抽象”約等于“接口”,實(shí)現(xiàn)一個(gè)抽象的最簡(jiǎn)單方式就是一個(gè)接口+一個(gè)實(shí)現(xiàn)類(lèi)。如果這個(gè)功能的邏輯很簡(jiǎn)單,這樣做無(wú)可厚非。但是,如果它的邏輯很復(fù)雜,還堅(jiān)持只用一個(gè)實(shí)現(xiàn)類(lèi),就會(huì)陷入“邏輯復(fù)雜、結(jié)構(gòu)簡(jiǎn)單”的困境中去。借助單一職責(zé)原則,我們就可以把復(fù)雜的邏輯劃分為不同的職責(zé)、并把這些職責(zé)拆分到不同的實(shí)現(xiàn)類(lèi)中去。例如,我們可以把完整功能劃分為“控制”+“處理”兩種職責(zé),“處理”職責(zé)又可以按“控制”職責(zé)的維度劃分為不同的子職責(zé),然后通過(guò)控制類(lèi)來(lái)選擇處理類(lèi),以此實(shí)現(xiàn)完整的業(yè)務(wù)功能。或者,我們可以把完整功能劃分為“基本功能”、“增強(qiáng)功能”、“增強(qiáng)功能2.0”、“增強(qiáng)功能最終版”、“再增強(qiáng)就不是人功能”等等,并通過(guò)不同功能的組合來(lái)實(shí)現(xiàn)不同的增強(qiáng)增強(qiáng)再增強(qiáng)功能。↑ 火車(chē)站的扳道工負(fù)責(zé)“控制”,站臺(tái)接站人員負(fù)責(zé)“處理”。 其次,我們常說(shuō),系統(tǒng)架構(gòu)是演化出來(lái)的。在這個(gè)演化過(guò)程中,我們往往會(huì)對(duì)已有的類(lèi)結(jié)構(gòu)做一些調(diào)整,如把某些類(lèi)放到新的接口下、為某些類(lèi)提取一個(gè)父類(lèi),等等。從面向?qū)ο笤O(shè)計(jì)的角度來(lái)說(shuō),這種架構(gòu)演化,本質(zhì)上都是開(kāi)發(fā)人員在梳理和調(diào)整系統(tǒng)中的抽象。例如,我最近做的兩項(xiàng)比較大的重構(gòu),其實(shí)都是在把散落在不同的Controller、Business、Service中的代碼整合到一個(gè)接口下的一套類(lèi)中,從而讓它們的業(yè)務(wù)抽象更清晰、易用。單一職責(zé)原則在這個(gè)過(guò)程中起到了什么作用呢?如果老代碼能夠遵循單一職責(zé)原則,那么重構(gòu)時(shí)就能輕松得多。很多時(shí)候我們?cè)O(shè)計(jì)一個(gè)業(yè)務(wù)抽象,就是在做一道“閱讀代碼并概括中心思想”的題目,最后得到的“中心思想”就是我們想要的業(yè)務(wù)抽象。很顯然,如果一個(gè)類(lèi)只做了一件事情,概括它的“中心思想”并抽取接口和抽象就變得非常容易;而如果一個(gè)類(lèi)做了好幾件沒(méi)什么關(guān)聯(lián)的事情,想要“一言以蔽之”就難多了。以單一職責(zé)為方向,重建業(yè)務(wù)抽象,是重構(gòu)的一種常用手段。例如,我們有個(gè)為不同用戶(hù)展示不同的首頁(yè)廣告的功能,它的邏輯是這樣的:↑ 其實(shí)邏輯并不復(fù)雜;問(wèn)題是代碼很……一言難盡。 這段邏輯并不算復(fù)雜。但是,在重構(gòu)之前,所有的代碼都在Controller里面,類(lèi)圖我就不放了,就那一個(gè)類(lèi),標(biāo)準(zhǔn)的“結(jié)構(gòu)簡(jiǎn)單、邏輯復(fù)雜”。這不僅使得我們很難調(diào)整廣告推送邏輯,而且,當(dāng)需要向某個(gè)新渠道開(kāi)放一個(gè)只包含廣告A和通用廣告的邏輯的新接口時(shí),由于原有邏輯完全攪在了一起,我們很難通過(guò)復(fù)用代碼來(lái)開(kāi)辟新的接口。所以,在開(kāi)新接口之前,我們重構(gòu)了一下這段代碼。重構(gòu)的方向,就是遵循單一職責(zé)原則,保證一個(gè)類(lèi)只負(fù)責(zé)一類(lèi)廣告的展示判斷。然后,借助某種不太直觀(guān)的類(lèi)結(jié)構(gòu)(其實(shí)就是責(zé)任鏈模式;不過(guò)設(shè)計(jì)模式先按下不表,留作后話(huà)),把這些類(lèi)組合成兩套不同的業(yè)務(wù)。最終的代碼邏輯和相關(guān)的類(lèi)被改成了這樣:↑ 每一個(gè)類(lèi)都變簡(jiǎn)單了;只是類(lèi)結(jié)構(gòu)變復(fù)雜了。 從這個(gè)例子也可以看出“邏輯簡(jiǎn)單、結(jié)構(gòu)復(fù)雜”的另一個(gè)優(yōu)點(diǎn):我們既不用改接口也不用改代碼,只要修改SpringIOC配置,就可以組合出不同的業(yè)務(wù)功能來(lái)。這種高復(fù)用性和高擴(kuò)展性,也是我們提倡單一職責(zé)原則的重要原因。=================================================================="高內(nèi)聚"是說(shuō),一個(gè)業(yè)務(wù)應(yīng)當(dāng)盡量把它所涉及的功能和代碼放到一個(gè)模塊中;"低耦合"則是說(shuō),一個(gè)業(yè)務(wù)應(yīng)當(dāng)盡量減少對(duì)其它業(yè)務(wù)或功能模塊的依賴(lài)。 如果我們把這里的“模塊”縮小范圍為單獨(dú)的一個(gè)類(lèi),就能很明顯地看出:?jiǎn)我宦氊?zé)原則在降低類(lèi)中功能和代碼的耦合性的同時(shí),也有可能降低它們的內(nèi)聚性。如果不遵守單一職責(zé)原則,最常見(jiàn)的情形就是一個(gè)接口下只有一個(gè)實(shí)現(xiàn)類(lèi),這個(gè)接口所涵蓋的所有邏輯全都放到了這一個(gè)類(lèi)中,即使這些功能可以進(jìn)一步拆分為不同的子功能或不同的低層抽象。顯然,這個(gè)模塊——或者說(shuō)這個(gè)類(lèi)——的內(nèi)聚性很高,但與此同時(shí),它的耦合性也很強(qiáng)。同一項(xiàng)業(yè)務(wù)在不同場(chǎng)景下的處理邏輯——如根據(jù)不同的理財(cái)產(chǎn)品配置和優(yōu)惠活動(dòng)規(guī)則計(jì)算用戶(hù)購(gòu)買(mǎi)理財(cái)產(chǎn)品的收益等——全都雜糅在一起,牽一發(fā)而動(dòng)全身,很容易在后續(xù)的擴(kuò)展和維護(hù)中產(chǎn)生莫名其妙的bug。↑ 這種動(dòng)輒幾百上千行的類(lèi)就是“一個(gè)接口一個(gè)實(shí)現(xiàn)類(lèi)”的造物。 而按照單一職責(zé)原則來(lái)設(shè)計(jì)類(lèi)結(jié)構(gòu)的話(huà),一個(gè)接口下往往會(huì)出現(xiàn)多個(gè)實(shí)現(xiàn)類(lèi);有時(shí)還會(huì)出現(xiàn)一些輔助接口或者抽象類(lèi)。在這種情況下,單靠其中任何一個(gè)實(shí)現(xiàn)類(lèi)、甚至依靠一部分實(shí)現(xiàn)類(lèi),都無(wú)法實(shí)現(xiàn)接口所定義的完整功能。因此,這種類(lèi)結(jié)構(gòu)的內(nèi)聚性比只有一個(gè)實(shí)現(xiàn)類(lèi)的結(jié)構(gòu)要低。但是顯然的,這種類(lèi)結(jié)構(gòu)的耦合性要更弱:不同的功能被分散到不同的實(shí)現(xiàn)類(lèi)中,僅通過(guò)某種結(jié)構(gòu)組合出完成的業(yè)務(wù)。無(wú)論我們要增、刪、改哪一項(xiàng)功能,都不影響其它功能。這樣不僅能減少線(xiàn)上的bug率,而且能提高開(kāi)發(fā)和測(cè)試效率——低耦合的優(yōu)點(diǎn)可以盡收囊中。前文提到過(guò)的首頁(yè)產(chǎn)品展示的類(lèi)、還有以前提到過(guò)的短信簽約操作的類(lèi),都可以佐證這一點(diǎn)。曾經(jīng)有同事和我討論過(guò)接口和實(shí)現(xiàn)類(lèi)的關(guān)系。他的代碼習(xí)慣是不寫(xiě)接口、直接寫(xiě)實(shí)現(xiàn)類(lèi):反正一個(gè)接口下只會(huì)有一個(gè)實(shí)現(xiàn)類(lèi)嘛,再聲明一個(gè)接口出來(lái)豈不是自討苦吃。而且對(duì)我的“一個(gè)接口、多個(gè)實(shí)現(xiàn)類(lèi)”的代碼習(xí)慣,他也頗有微詞:這么多實(shí)現(xiàn)類(lèi),在需要注入時(shí)不知道該用哪一個(gè);看代碼時(shí)也不知道該看哪一個(gè)實(shí)現(xiàn)類(lèi)。其實(shí)他所說(shuō)的,就是高內(nèi)聚和低內(nèi)聚之間的幾個(gè)優(yōu)缺點(diǎn)對(duì)比。不用說(shuō),我自然是用低耦合和強(qiáng)耦合之間的優(yōu)缺點(diǎn)對(duì)比來(lái)反駁他的。↑ 同一接口、多個(gè)實(shí)現(xiàn)類(lèi),確實(shí)容易“找不著北”。 我倆誰(shuí)對(duì)誰(shuí)錯(cuò)呢?脫離了具體場(chǎng)景,自然是“公說(shuō)公有理婆說(shuō)婆有理”。單一職責(zé)原則也是一樣:到底要不要遵守它、要遵守到什么程度,都要視具體場(chǎng)景做出取舍。如果業(yè)務(wù)確實(shí)簡(jiǎn)單,那么一個(gè)實(shí)現(xiàn)類(lèi)就可以搞定;而無(wú)論業(yè)務(wù)邏輯再怎么復(fù)雜,也沒(méi)必要針對(duì)每個(gè)if-else都創(chuàng)建一個(gè)子類(lèi)出來(lái)。大多數(shù)時(shí)候,“執(zhí)兩用中”是取舍的最佳方案。==================================================================繼承和多態(tài)是履行單一職責(zé)原則所必不可少又實(shí)用便捷的手段。相比之下,封裝與單一職責(zé)原則的關(guān)系就更疏遠(yuǎn)一些了。前面我們提到,“單一職責(zé)原則能夠幫助我們對(duì)抽象做進(jìn)一步的拆分”。拆分的方式有兩種:一種是縱向拆分,也就是增加繼承層次,把抽象內(nèi)的各種職責(zé)逐層拆分;另一種則是橫向拆分,也就是增加實(shí)現(xiàn)類(lèi)個(gè)數(shù),把各種職責(zé)分?jǐn)偟狡郊?jí)的實(shí)現(xiàn)類(lèi)中。顯然,縱向拆分主要是通過(guò)繼承,把一部分通用的職責(zé)(如方法、數(shù)據(jù))放在父類(lèi)中,而子類(lèi)只需要專(zhuān)注于自己的職責(zé)即可。這樣,父類(lèi)和子類(lèi)各司其職,相得益彰。橫向拆分則主要通過(guò)多態(tài),在保持對(duì)外的抽象(接口或者父類(lèi))不變的前提下,把不同的職責(zé)拆分到不同的子類(lèi)中。這樣,不同的實(shí)現(xiàn)類(lèi)之間就可以獨(dú)立自主地和平共處了。↑ 對(duì)類(lèi)職責(zé)做橫向和縱向的拆分。有沒(méi)有“合縱連橫”的感覺(jué)? 當(dāng)然,縱向拆分也是一種多態(tài);橫向拆分也要用到繼承。而且,這兩者并不互斥??v向拆分到某一層級(jí)時(shí),也可以做橫向拆分;橫向拆分出來(lái)的類(lèi)如果職責(zé)仍然太多,也可以通過(guò)縱向拆分進(jìn)一步“單一職責(zé)”。不過(guò),既然說(shuō)到了繼承,這里還是要再重復(fù)一遍:慎用繼承。繼承固然能讓我們方便快捷地復(fù)用代碼,但是它帶來(lái)的強(qiáng)耦合性就像一塊埋在腦袋里的彈片一樣,不知道什么時(shí)候就會(huì)讓我們頭疼不已。而且,很多的繼承關(guān)系其實(shí)都可以改用組合來(lái)實(shí)現(xiàn)。在不能百分百確定應(yīng)該使用繼承的情況下,個(gè)人建議還是盡量使用組合來(lái)復(fù)用代碼、擴(kuò)展功能。雖然說(shuō)封裝與單一職責(zé)原則的關(guān)系不如繼承和多態(tài)那么密切,但二者多少還是有些聯(lián)系的。封裝隱含有“封裝變化”、“隔離變化”的含義。而單一職責(zé)原則所說(shuō)的“一個(gè)類(lèi)應(yīng)該只有一個(gè)發(fā)生變化的原因”也隱含有“一種變化只影響一個(gè)類(lèi)(的代碼)”的含義。兩相對(duì)照,不謀而合。↑ 不謀而合,惺惺相惜。 說(shuō)到“隔離變化”,五大設(shè)計(jì)原則中最能體現(xiàn)這一點(diǎn)的當(dāng)屬開(kāi)閉原則。不過(guò)說(shuō)到“開(kāi)閉”嘛,就“請(qǐng)聽(tīng)下回分解”吧。=================================
|