注:正文中的引用是直接引用作者的話,兩條橫線中間的段落的是我自己的觀點(diǎn)倘核,其他大約都可以算是筆記了陕见。
「Clean Code」這本書(shū)從這一章開(kāi)始文風(fēng)有些變化秘血,感覺(jué)比較亂,很多概念在之前的章節(jié)也提到過(guò)评甜,因?yàn)檫@本書(shū)的某些章節(jié)是不同的人編寫(xiě)的灰粮,所以這種情況也難免,所以可能會(huì)有些小節(jié)我會(huì)幾句話簡(jiǎn)單帶過(guò)忍坷。
本章講的是類的組織結(jié)構(gòu)粘舟,其實(shí)很多這些概念我們?cè)趯W(xué)校里學(xué)習(xí)OOP時(shí)可能都有學(xué)到過(guò)奈嘿,有些人可能會(huì)覺(jué)得講得比較虛系羞,但文中確實(shí)有些細(xì)節(jié)還是解開(kāi)了一些之前的疑惑荔茬,姑且當(dāng)做復(fù)習(xí)面向?qū)ο蟮母拍钜埠谩?/p>
在前面的章節(jié)中詳細(xì)討論了命名轻庆、方法和數(shù)據(jù)結(jié)構(gòu)等等這些概念,它們能夠幫助我們更好地理解在代碼行或者代碼塊的級(jí)別里如何寫(xiě)出簡(jiǎn)潔優(yōu)雅晰骑。在此基礎(chǔ)上适秩,我們還是要在更高的層面上去探究代碼簡(jiǎn)潔之道。在現(xiàn)代的高級(jí)語(yǔ)言編程世界里硕舆,類是系統(tǒng)的基本組成部分秽荞,這章就著重討論一下如何寫(xiě)出好的類。
類的組織結(jié)構(gòu)
對(duì)于類的代碼結(jié)構(gòu)抚官,Java中有一套不成文的約定:
- 一個(gè)類應(yīng)該以一系列的常量和變量定義作為開(kāi)始
- 如果有公共靜態(tài)常量扬跋,它們應(yīng)該放在最前邊
- 接下來(lái)是私有的靜態(tài)常量
- 接下來(lái)是私有的實(shí)例變量
- 類中不應(yīng)該有公共的變量
- 緊接著是公共的方法
- 一些私有的方法應(yīng)該緊接著它被調(diào)用的共有方法后邊
封裝
在OOP中很多概念都是相通的,封裝作為OOP的一個(gè)基本概念突出了「開(kāi)閉原則」的重要性凌节,它很好地解決了一些擴(kuò)展性的問(wèn)題胁住,它使得接口的提供者可以屏蔽此接口的具體實(shí)現(xiàn),從而可以在一個(gè)較自由的范圍內(nèi)去修改自己的實(shí)現(xiàn)刊咳。
按照封裝的概念,一個(gè)類中的所有變量都應(yīng)該是私有的儡司,但同時(shí)也不要對(duì)此概念太過(guò)執(zhí)著娱挨,前邊的章節(jié)也提到過(guò)測(cè)試的重要性,有時(shí)候測(cè)試需要某些類的變量可訪問(wèn)捕犬,那么可以考慮給它們賦予protected
屬性跷坝。但應(yīng)該盡可能的保證封裝的特性。
類應(yīng)該盡可能的「小」
在函數(shù)的那一章我們提到過(guò)方法應(yīng)該設(shè)計(jì)的盡可能的小碉碉,我們衡量函數(shù)使用代碼行數(shù)柴钻,在這里我們衡量類使用「職責(zé)」。
一個(gè)類的職責(zé)應(yīng)該是唯一的垢粮,這才符合OOP對(duì)現(xiàn)實(shí)世界的模擬的概念贴届。職責(zé)往往是和代碼的行數(shù)正相關(guān),但它們并不是完全正相關(guān)的蜡吧,如代碼9-1中所示:
代碼9-1
public class SuperDashboard extends JFrame implements MetaDataUser{
public Component getLastFocusedComponent()
public void setLastFocused(Component lastFocused)
public int getMajorVersionNumber()
public int getMinorVersionNumber()
public int getBuildNumber()
}
這個(gè)類只包含了5個(gè)函數(shù)毫蚓,那么它是不是已經(jīng)足夠小了呢?非也昔善,它包含了兩個(gè)不同的職責(zé)——它同時(shí)管理「版本號(hào)」與「某個(gè)JFrame組件」元潘。
單職責(zé)原則(SRP)
SRP的意思是說(shuō)一個(gè)類(或者一個(gè)模塊)應(yīng)該有且只有一個(gè)要修改它的原因(職責(zé))。
比如代碼9-1中所示那樣君仆,我們可能有兩個(gè)原因要去修改類SuperDashboard
翩概,一個(gè)是版本號(hào)改變了牲距,另一個(gè)是獲取組件的方法變了。誠(chéng)然钥庇,當(dāng)我們修改了獲取lastFocus
組件的方法時(shí)牍鞠,往往是要修改版本號(hào)的,但是反過(guò)來(lái)就不一定了上沐。
SRP是OOP中最重要的設(shè)計(jì)理念之一皮服,但同時(shí)也是最常被違反的理念之一〔瘟「使軟件可以工作」和「使軟件簡(jiǎn)潔優(yōu)雅」是兩個(gè)截然不同的的工作龄广,我們常常沒(méi)有時(shí)間也沒(méi)有精力同時(shí)關(guān)注這兩者,然后就只關(guān)注前者了蕴侧。
在中國(guó)當(dāng)下的現(xiàn)實(shí)環(huán)境是择同,很多代碼的需求方(往往是老板)根本不在乎代碼的可維護(hù)性、可擴(kuò)展性甚至健壯性净宵,他們的要求常常是軟件快速上線敲才,而不是簡(jiǎn)潔優(yōu)雅但要延期上線的軟件。此外择葡,還有有很多代碼寫(xiě)完之后可能永遠(yuǎn)也不會(huì)被維護(hù)和修改紧武,所以「使軟件簡(jiǎn)潔優(yōu)雅」慢慢地塊要變成一種個(gè)人追求,變成了「大家都說(shuō)這樣做更好敏储,但真正這么做的卻很少」阻星。
我倒是覺(jué)得盡管在實(shí)際的編程工作中不得不不斷地進(jìn)行妥協(xié),但是只要把clean code的理念放在心中已添,并用它來(lái)審視自己的代碼妥箕,我們總是會(huì)寫(xiě)出越來(lái)越好的代碼。編程是如此更舞,人生何嘗不是如此畦幢。
問(wèn)題是很多人認(rèn)為軟件「可以工作」的那一刻,我們的工作就結(jié)束了缆蝉。我們接下來(lái)要做的是解決下一個(gè)問(wèn)題而不是回過(guò)頭來(lái)把這些超級(jí)類分解成解耦的小單元宇葱。與此同時(shí),還有很多人很害怕看到「大量的小而職責(zé)單一的類」刊头,覺(jué)得那樣會(huì)使他們很難去從大方向上理解整個(gè)系統(tǒng)贝搁。事實(shí)則恰恰相反,大量的小的職責(zé)單一的解耦的類往往帶來(lái)更多的好處芽偏。
我們的目標(biāo)是這樣的:我們的系統(tǒng)由大量的小的職責(zé)單一的類組成雷逆,而不是少數(shù)幾個(gè)超級(jí)大類。每一個(gè)類都只與少數(shù)幾個(gè)其他類進(jìn)行交互(這點(diǎn)有點(diǎn)像迪米特法則)污尉。
內(nèi)聚
內(nèi)聚的概念是這樣的:一個(gè)類應(yīng)該只有少數(shù)的幾個(gè)實(shí)例變量膀哲,這個(gè)類的每個(gè)方法都應(yīng)該操作這個(gè)類中的一個(gè)或多個(gè)實(shí)例變量往产。通常一個(gè)方法操作的實(shí)例變量越多,那么這個(gè)方法對(duì)于這個(gè)類來(lái)說(shuō)聚合性就越高某宪。一個(gè)類中的所有方法都操作了這個(gè)類中的所有實(shí)例變量仿村,那么這個(gè)類就是聚合型最高的。
但是兴喂,通常來(lái)說(shuō)這樣的超級(jí)內(nèi)聚的類不太可能出現(xiàn)蔼囊,也不建議去建立這樣的類。但我們還是想要一個(gè)類的內(nèi)聚性是高的衣迷,這表明這個(gè)類中的幾個(gè)組成部分是互相依賴不可分割的畏鼓。如代碼9-2中所示的一個(gè)堆棧的簡(jiǎn)單實(shí)現(xiàn),就是一個(gè)內(nèi)聚性很高的例子:
代碼9-2
public class Stack {
private int topOfStack = 0;
List<Integer> elements = new LinkedList<Integer>();
public int size() {
return topOfStack;
}
public void push(int element) {
topOfStack++;
elements.add(element);
}
public int pop() throws PoppedWhenEmpty {
if (topOfStack == 0)
throw new PoppedWhenEmpty();
int element = elements.get(--topOfStack);
elements.remove(topOfStack);
return element;
}
}
在這個(gè)類中出了方法size()
之外壶谒,其他幾個(gè)方法都同時(shí)使用到了這個(gè)類中的兩個(gè)變量云矫。所以寫(xiě)出高內(nèi)聚的類的訣竅就是,保持類中的變量個(gè)數(shù)很少汗菜,方法很小让禀。如果一個(gè)你代碼中某個(gè)類的內(nèi)聚性很低,那么你就要考慮一下陨界,是否要把它拆分成幾個(gè)更小的類了巡揍。
維護(hù)類的高內(nèi)聚往往會(huì)帶來(lái)更小的類
只要你不斷的將大的方法拆分成小的方法,最直接的結(jié)果就是你會(huì)看到越來(lái)越多的類菌瘪。
舉例來(lái)說(shuō)腮敌,我們經(jīng)常會(huì)碰到一個(gè)場(chǎng)景,我們想把一個(gè)超級(jí)方法中的某一個(gè)邏輯(可能是幾行代碼)抽出來(lái)重構(gòu)成為一個(gè)新的方法麻车,然后抽取之后的新方法需要傳入4個(gè)在這個(gè)超級(jí)方法中定義的變量,這種情形下斗这,最好就是把這4個(gè)變量編程類級(jí)別的變量动猬,這樣我們抽取的這個(gè)新方法就不需要傳入任何的參數(shù)了。
但是表箭,這樣做之后這個(gè)類的內(nèi)聚性就降低了——這4個(gè)變量只在兩個(gè)方法中被調(diào)用——但是這種「有幾個(gè)方法想要分享幾個(gè)變量」的行為不正是類定義的由來(lái)嗎赁咙。所以,一旦你的類的內(nèi)聚性降低時(shí)免钻,就去著手把它拆分為更小的類吧彼水。
所以,拆分類可以從拆分超級(jí)方法開(kāi)始极舔,這樣往往能給我們帶來(lái)一個(gè)更清晰的類的組織結(jié)構(gòu)凤覆。
為了變化而設(shè)計(jì)
對(duì)于大多數(shù)的系統(tǒng),變化是持續(xù)發(fā)生的拆魏。每次發(fā)生改變盯桦,都可能對(duì)我們的現(xiàn)有系統(tǒng)造成威脅慈俯,那么我們?cè)O(shè)計(jì)系統(tǒng)中「類的組織結(jié)構(gòu)」時(shí)就要盡可能降低這種風(fēng)險(xiǎn)。
然后在這個(gè)小節(jié)作者舉了個(gè)使用abstract類來(lái)解決對(duì)類的修改的問(wèn)題拥峦√欤「對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉」最好的一個(gè)實(shí)現(xiàn)就是使用抽象類略号,因?yàn)閷?duì)于此抽象概念增加時(shí)只需要多寫(xiě)一個(gè)此抽象類的實(shí)現(xiàn)類刑峡,而不是去修改現(xiàn)有的實(shí)現(xiàn)類。
隔離變化
需求會(huì)不斷地變化玄柠,所以我們的代碼實(shí)現(xiàn)也會(huì)不斷地變化突梦。使用抽象類可以很大程度上地隔離這種變化。
這一小節(jié)的宗旨就是說(shuō)要使用面向接口編程随闪,使得此接口的調(diào)用者對(duì)接口依賴而不是對(duì)實(shí)現(xiàn)依賴阳似,這樣就實(shí)現(xiàn)了「隔離變化」。