Deep Module深模塊之軟件設(shè)計(jì)
正文
類是不是越小越好?最近在讀John Ousterhout的《A Philosophy of Software Design》,感到作者文筆流暢,書(shū)中內(nèi)容具有啟發(fā)性。這里摘要一部分內(nèi)容,以供開(kāi)發(fā)工作中的參考、學(xué)習(xí)。
在軟件復(fù)雜度的管理當(dāng)中,最重要的技術(shù)之一是通過(guò)對(duì)系統(tǒng)的設(shè)計(jì),使開(kāi)發(fā)者任何在時(shí)候都只需要面對(duì)整體復(fù)雜度中的一小部分。這個(gè)過(guò)程被稱為模塊化設(shè)計(jì)。
復(fù)雜度是什么?在本文中,復(fù)雜度的定義是:和軟件系統(tǒng)結(jié)構(gòu)有關(guān)的、會(huì)導(dǎo)致理解和修改系統(tǒng)變困難的東西。
1,模塊化設(shè)計(jì)
在模塊設(shè)計(jì)中,軟件系統(tǒng)被分解為相對(duì)獨(dú)立的模塊集合。模塊的形式多種多樣,可以是類、子系統(tǒng)、或服務(wù)等。在理想的世界中,每個(gè)模塊都完全獨(dú)立于其它模塊:開(kāi)發(fā)者在任何模塊中工作的時(shí)候,都不需要知道有關(guān)其它模塊的任何知識(shí)。在這種理想狀態(tài)下,系統(tǒng)復(fù)雜度取決于系統(tǒng)中復(fù)雜度最高的模塊。
當(dāng)然,實(shí)踐與理想不同,系統(tǒng)模塊間總會(huì)多少有些依賴。當(dāng)一個(gè)模塊變化時(shí),其它模塊可能也需要隨之而改變。模塊化設(shè)計(jì)的目標(biāo)就是最小化模塊間的依賴。
為了管理依賴,我們可以把模塊看成兩部分:接口和實(shí)現(xiàn)。
接口包含了全部的在調(diào)用該模塊時(shí)需要的信息。接口只描述模塊做什么,但不會(huì)包含怎么做。
完成接口所做出的承諾的代碼被稱為實(shí)現(xiàn)。
在一個(gè)特定模塊內(nèi)部進(jìn)行工作的開(kāi)發(fā)者必須知道的信息是:當(dāng)前模塊的接口和實(shí)現(xiàn)+其它被該模塊使用的模塊的接口。他不需要理解其它模塊的實(shí)現(xiàn)。
在本文中,包含接口/實(shí)現(xiàn)的任何代碼單元,都是模塊。面向?qū)ο笳Z(yǔ)言中的類是模塊,類中的方法也是模塊,非面向?qū)ο笳Z(yǔ)言中的函數(shù)也是模塊。高層的子系統(tǒng)和服務(wù)也可以被看作模塊,它們的接口也許是多種形式的,比如內(nèi)核調(diào)用或HTTP請(qǐng)求。本文中的大部分內(nèi)容針對(duì)的是類,但這些技術(shù)和理論對(duì)其它類型的模塊也有效。
好模塊的接口遠(yuǎn)遠(yuǎn)比實(shí)現(xiàn)更簡(jiǎn)單。這樣的模塊有2個(gè)優(yōu)點(diǎn)。首先,簡(jiǎn)單的接口最小化了模塊施加給系統(tǒng)其余部分的復(fù)雜度。其次,如果修改模塊時(shí)可以不修改它的接口,那么其他模塊就不會(huì)被修改所影響。如果模塊的接口遠(yuǎn)遠(yuǎn)比實(shí)現(xiàn)簡(jiǎn)單,那么就更有可能在不改動(dòng)接口的情況對(duì)模塊進(jìn)行修改。
2,接口里有什么
接口中包含2種信息:正式的和非正式的。
正式的信息在代碼中被顯式指定,程序語(yǔ)言可以檢查其中的部分正確性。比如,方法的簽名就是正式的信息,它包含參數(shù)的名稱和類型,返回值的類型,異常的信息。很多程序語(yǔ)言可以保證代碼中對(duì)方法的調(diào)用提供了與方法定義相匹配的參數(shù)值。
接口里面也包含非正式的元素。非正式部分無(wú)法被程序語(yǔ)言理解或強(qiáng)制執(zhí)行。接口的非正式部分包含一些高層行為,比如函數(shù)會(huì)根據(jù)某個(gè)參數(shù)的內(nèi)容刪除具有相應(yīng)名字的文件。如果某個(gè)類的使用存在某種限制,比如方法的調(diào)用需要符合特定順序,那這也屬于接口的一部分。凡是開(kāi)發(fā)者在使用模塊時(shí)需要了解的信息,都可以算作模塊接口的一部分。接口的非正式信息只能通過(guò)注釋等方式描述,程序語(yǔ)言無(wú)法確保描述是完整而準(zhǔn)確的。大部分接口的非正式信息都比正式信息要更多、更復(fù)雜。
清晰的接口定義有助于開(kāi)發(fā)者了解在使用模塊時(shí)需要知道的信息,從而避免一些問(wèn)題。
3,抽象
抽象這一術(shù)語(yǔ)和模塊設(shè)計(jì)思想的關(guān)系很近。抽象是實(shí)體的簡(jiǎn)化視圖,省略了不重要的細(xì)節(jié)。抽象很有用,它可以使我們對(duì)細(xì)節(jié)的思考和操縱變簡(jiǎn)單。
在模塊化編程中,每個(gè)模塊通過(guò)接口提供其抽象。抽象代表了函數(shù)功能的簡(jiǎn)化視圖。在函數(shù)抽象的立場(chǎng)上,實(shí)現(xiàn)的細(xì)節(jié)是不重要的,所以它們被省略了。
“不重要”這個(gè)詞很關(guān)鍵。如果沒(méi)有忽略掉不重要的細(xì)節(jié),那么抽象會(huì)變得復(fù)雜,會(huì)增加開(kāi)發(fā)者的認(rèn)知負(fù)擔(dān);如果忽略掉了重要的細(xì)節(jié),那么抽象會(huì)變得錯(cuò)誤,失去對(duì)實(shí)踐的指導(dǎo)意義。設(shè)計(jì)抽象的關(guān)鍵是理解什么是重要的,并尋找最小化重要信息的設(shè)計(jì)。
依賴抽象來(lái)管理復(fù)雜度不是編程的專利,它遍布在我們的日常生活中。就像車子會(huì)提供一個(gè)簡(jiǎn)單抽象來(lái)讓我們駕駛,并不需要我們理解發(fā)動(dòng)機(jī)、電池、ABS之類的東西。
4,深模塊
最好的模塊提供了強(qiáng)大的功能,又有著簡(jiǎn)單的接口。術(shù)語(yǔ)“深”可以用于描述這種模塊。為了讓深度的概念可視化,試想每個(gè)模塊由一個(gè)長(zhǎng)方形表示,如下圖,
長(zhǎng)方形的面積大小和模塊實(shí)現(xiàn)的功能多少成比例。頂部邊代表模塊的接口,邊的長(zhǎng)度代表它的復(fù)雜度。最好的模塊是深的:他們有很多功能隱藏在簡(jiǎn)單的接口后。深模塊是好的抽象,因?yàn)樗话炎约簝?nèi)部的一小部分復(fù)雜度暴露給了用戶。
淺模塊的接口復(fù)雜,功能卻少,它沒(méi)有隱藏足夠的復(fù)雜度。
可以從成本與收益的角度思考模塊深度。模塊提供的收益是它的功能。模塊的成本(從系統(tǒng)復(fù)雜度的角度考慮)是它的接口。接口代表了模塊施加給系統(tǒng)其余部分的復(fù)雜度。接口越小而簡(jiǎn)單,它引入的復(fù)雜度就越少。好的模塊就是那些成本低收益高的模塊。
某些語(yǔ)言中的垃圾回收(GC)是深模塊的例子之一。這個(gè)模塊沒(méi)有接口,它在需要回收無(wú)用內(nèi)存的場(chǎng)景下不可見(jiàn)地工作。在系統(tǒng)中加入垃圾回收的做法,縮小了系統(tǒng)的總接口,因?yàn)檫@種做法消除了用于釋放對(duì)象的接口。垃圾回收的具體實(shí)現(xiàn)是相當(dāng)復(fù)雜的,但這一復(fù)雜度在實(shí)際使用程序語(yǔ)言的時(shí)候是隱藏的。
5,淺模塊
相對(duì)的,淺模塊就是接口相對(duì)功能而言很復(fù)雜的模塊。下面是個(gè)可能有些極端的例子,
private void addNullValueForAttribute(String attribute) { data.put(attribute, null); }
從復(fù)雜度管理的角度來(lái)看,該方法把事情變?cè)懔?。它沒(méi)有提供抽象,因?yàn)樗械墓δ芏际窃诮涌谏峡梢?jiàn)的。思考這一接口并不會(huì)比思考它的完整實(shí)現(xiàn)更簡(jiǎn)單。如果方法有合適的文檔,文檔也不會(huì)比方法的代碼具有更多信息。相比于直接操作data,它的長(zhǎng)名字甚至?xí)?dǎo)致開(kāi)發(fā)者敲擊鍵盤(pán)的次數(shù)變多。這種方法增加了復(fù)雜度(引入了一個(gè)需要開(kāi)發(fā)者了解的新接口),但并沒(méi)有提供與之相應(yīng)的收益。注意:小的模塊會(huì)更傾向于變淺。
6,Classitis
當(dāng)今,深模塊的價(jià)值并沒(méi)有被廣為接受。一般常識(shí)是類需要小,而不是深。學(xué)生們被告知:類設(shè)計(jì)中最重要的事情是把大類拆分成更小的類。相似的建議還包括:“要把方法行數(shù)大于N的方法分成多個(gè)方法”,有時(shí)候N甚至只有10這么小。這會(huì)導(dǎo)致大量的淺模塊,增加系統(tǒng)的總復(fù)雜度。
極端的“類應(yīng)該小”的做法是一種綜合癥的表現(xiàn),這種癥狀可以被稱為Classitis。它源于一種錯(cuò)誤思維:“類是好的,所以越多類越好”。這種思想最終會(huì)導(dǎo)致系統(tǒng)層面積累了巨大的復(fù)雜度,程序風(fēng)格也會(huì)變得啰嗦。
7,例子
Java類庫(kù)可能是Classitis的最明顯例子之一。Java語(yǔ)言本身不需要很多小類,但Classitis文化可能已經(jīng)在Java語(yǔ)言社區(qū)扎了根。比如,為了打開(kāi)文件讀取其中的序列化對(duì)象,你必須創(chuàng)建多種對(duì)象:
FileInputStream fileStream = new FileInputStream(fileName); BufferedInputStream bufferedStream = new BufferedInputStream(fileStream); ObjectInputStream objectStream = new ObjectInputStream(bufferedStream);
FileInputStream對(duì)象只提供初步的I/O,它不具備緩存I/O的能力,也不能讀寫(xiě)序列化對(duì)象。BufferedInputStream和ObjectInputStream分別提供了后面兩項(xiàng)功能。文件打開(kāi)之后,fileStream和bufferedStream就沒(méi)用了,未來(lái)的操作只會(huì)用到objectStream.。
必須顯式單獨(dú)創(chuàng)建BufferedInputStream對(duì)象來(lái)請(qǐng)求緩存,這很煩人而且易出錯(cuò)。如果開(kāi)發(fā)者忘記創(chuàng)建它,就不會(huì)有緩存,而且I/O會(huì)慢。大概Java開(kāi)發(fā)者會(huì)辯解說(shuō),不是所有人都需要緩存,所以它不應(yīng)該包含在基本讀寫(xiě)機(jī)制中。他們也許會(huì)說(shuō)讓緩存獨(dú)立更好,借此用戶可以選擇是否使用它。提供選擇空間當(dāng)然很好,但接口需要設(shè)計(jì)為對(duì)常用場(chǎng)景盡可能簡(jiǎn)單,幾乎所有文件I/O用戶都想使用緩存,所以就應(yīng)該默認(rèn)提供它。對(duì)于少數(shù)不需要的情況,庫(kù)可以提供機(jī)制以禁用。禁用緩存的機(jī)制應(yīng)該明確地在接口中分離(例如,為FileInputStream提供不同的構(gòu)造器,或者通過(guò)一個(gè)方法禁用/替換緩存機(jī)制),這樣大部分開(kāi)發(fā)者甚至不需要意識(shí)到它的存在。
8,結(jié)論
通過(guò)將模塊的接口和實(shí)現(xiàn)分離,我們可以對(duì)系統(tǒng)的其它部分隱藏實(shí)現(xiàn)的復(fù)雜度。模塊的使用者只需要理解接口提供的抽象。在設(shè)計(jì)類和其它模塊時(shí),最重要的問(wèn)題是讓它們深,它們要對(duì)常見(jiàn)用例有足夠簡(jiǎn)單的接口,但同時(shí)依然提供強(qiáng)大的功能。這就最大化地隱藏了復(fù)雜度。
以上就是Deep Module深模塊之軟件設(shè)計(jì)的詳細(xì)內(nèi)容,更多關(guān)于Deep Module深模塊的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java實(shí)現(xiàn)驗(yàn)證文件名有效性的方法詳解
在本文中,我們將討論使用?Java?驗(yàn)證一個(gè)給定的字符串是否具有操作系統(tǒng)的有效文件名的不同方法,文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-09-09mybatis如何獲取剛剛新插入數(shù)據(jù)的主鍵值id
這篇文章主要介紹了mybatis如何獲取剛剛新插入數(shù)據(jù)的主鍵值id問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08Spring入門(mén)配置和DL依賴注入實(shí)現(xiàn)圖解
這篇文章主要介紹了Spring入門(mén)配置和DL依賴注入實(shí)現(xiàn)圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10Java創(chuàng)建,編輯與刪除Excel迷你圖表的實(shí)現(xiàn)方法
迷你圖是Excel工作表單元格中表示數(shù)據(jù)的微型圖表。本文將通過(guò)Java代碼示例介紹如何在Excel中創(chuàng)建迷你圖表,以及編輯和刪除表格中的迷你圖表,需要的可以參考一下2022-05-05SpringBoot通過(guò)注解注入Bean的幾種方式解析
這篇文章主要為大家介紹了SpringBoot注入Bean的幾種方式示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-03-03解決fcitx輸入法在IDEA中輸入法候選框無(wú)法跟隨光標(biāo)移動(dòng)的問(wèn)題
這篇文章主要介紹了解決fcitx輸入法在Intellij IDEA開(kāi)發(fā)工具中輸入法候選框無(wú)法跟隨光標(biāo)移動(dòng)的問(wèn)題,代碼簡(jiǎn)單易懂對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10