很好的文章,下面是摘錄,請直接下載原文閱讀。
1.1 設計正在“腐爛”的征兆(Symptoms of Rotting Design)
有四個(gè)主要的征兆告訴我們該軟件設計正在“腐爛”中。它們并不是互相獨立的,而是互相關(guān)聯(lián),它們是過(guò)于僵硬、過(guò)于脆弱、不可重用性和粘滯性過(guò)高。
1. 過(guò)于僵硬Rigidity
Rigidity 致使軟件難以更改,每一個(gè)改動(dòng)都會(huì )造成一連串的互相依靠的模塊的改動(dòng),項目經(jīng)理不敢改動(dòng),因為他永遠也不知道一個(gè)改動(dòng)何時(shí)才能完成。
2. 過(guò)于脆弱Fragility
Fragility 致使當軟件改動(dòng)時(shí),系統會(huì )在許多地方出錯。并且錯誤經(jīng)常會(huì )發(fā)生在概念上與改動(dòng)的地方?jīng)]有聯(lián)系的模塊中。這樣的軟件無(wú)法維護,每一次維護都使軟件變得更加難以維護。(惡性循環(huán))
3. 不可重用性immobility
immobility 致使我們不能重用在其它項目中、或本項目中其它位置中的軟件。工程師發(fā)現將他想重用的部分分離出來(lái)的工作量和風(fēng)險太大,足以抵消他重用的積極性,因此軟件用重寫(xiě)代替了重用。
4. 粘滯性過(guò)高viscosity
viscosity有兩種形式:設計的viscosity和環(huán)境的viscosity。
當需要進(jìn)行改動(dòng)時(shí),工程師通常發(fā)現有不止一個(gè)方法可以達到目的。但是這些方法中,一些會(huì )保留原有的設計不變,而另外一些則不會(huì )(也就是說(shuō),這些人是hacks)。一個(gè)設計如果使工程師作錯比作對容易得多,那么這個(gè)設計的viscosity 就會(huì )很高。
環(huán)境的viscosity高是指開(kāi)發(fā)環(huán)境速度很慢且效率很低。
2 面向對象的類(lèi)設計原則
2.1 開(kāi)放關(guān)閉原則The Open Closed Principle (OCP)
A module should be open for extension but closed for modification.一個(gè)模塊應該只在擴展的時(shí)候被打開(kāi)(暴露模塊內部),在修改的時(shí)候是關(guān)閉的(模塊是黑盒子)。
在所有的面向對象設計原則中,這一條最重要。該原則是說(shuō):我們應該能夠不用修改模塊的源代碼,就能更改模塊的行為。
2.1.1 動(dòng)態(tài)多態(tài)性(Dynamic Polymorphism)
2.1.2 靜態(tài)多態(tài)性(Static Polymorphism)
另外一種使用OCP的技術(shù)就是使用模板或范型,如Listing 2-3。LogOn函數不用修改代碼就可以擴展出多種類(lèi)型的modem。
2.1.3 OCP的體系結構目標(Architectural Goals of the OCP)
通過(guò)遵照OCP應用這些技術(shù),我們能創(chuàng )建不用更改內部代碼就可以被擴展的模塊。這就是說(shuō),在將來(lái)我們給模塊增添新功能是,只要增加新的代碼,而不用更改原先的代碼。 第 3 頁(yè),共 17 頁(yè)
使軟件完全符合OCP可能是很難的,但即使只是部分符合OCP,整個(gè)軟件的結構性能也會(huì )有很大的提高。我們應該記住,讓變化不要波及已經(jīng)正常工作的代碼總是好的。
2.2 Liskov 替換原則The Liskov Substitution Principle(LSP)
Subclasses should be substitutable for their base classes.子類(lèi)應該可以替換其基類(lèi)。
如下圖2-14所示。Derived類(lèi)應該能替換其Base類(lèi)。也就是說(shuō),Base基類(lèi)的一個(gè)用戶(hù)User如果被傳遞給一個(gè)Devrived類(lèi)而不是Base類(lèi)作為參數,也能正常的工作。
2.3 依賴(lài)性倒置原則The Dependency Inversion Principle (DIP)1
Depend upon Abstractions. Do not depend upon concretions.依賴(lài)抽象,不要依賴(lài)具體。
如果說(shuō)OCP聲明了OO體系結構的目的,DIP則闡述了其主要機制。依賴(lài)性倒置的策略就是要依賴(lài)接口、或抽象函數、或抽象類(lèi),而不是依賴(lài)于具體的函數和類(lèi)。這條原則就是支持組件設計、COM、CORBA、EJB等等的背后力量。
2.3.1 依賴(lài)抽象Depending upon Abstractions.
實(shí)現該原則十分簡(jiǎn)單。設計中的每一個(gè)依賴(lài)都應該是接口、抽象類(lèi),不要依賴(lài)任何一個(gè)具體類(lèi)。
顯然這樣的限制比較嚴峻,但是我們應該盡可能的遵守這條原則。原因很簡(jiǎn)單,具體的模塊變化太多,抽象的則變化少得多。而且,抽象是“鉸鏈”點(diǎn),在這些位置,設計可以彎曲或者擴展,而不用進(jìn)行更改(OCP)。
2.4 接口隔離原則The Interface Segregation Principle (ISP)‘
Many client specific interfaces are better than one general purpose interface多個(gè)和客戶(hù)相關(guān)的接口要好于一個(gè)通用接口。
ISP是另一條在底層支持組件如COM技術(shù)的原則。沒(méi)有它,組件和類(lèi)的易用性和重用性都會(huì )大打折扣。該原則的實(shí)質(zhì)很簡(jiǎn)單:如果一個(gè)類(lèi)有幾個(gè)使用者,與其讓這個(gè)類(lèi)載入所有使用者需要使用的所有方法,還不如為每一個(gè)使用者創(chuàng )建一個(gè)特定的接口,并讓該類(lèi)分別實(shí)現這些接口。
3 包體系結構的原則Principles of Package Architecture
類(lèi)是必不可少的,但對于組織一個(gè)設計來(lái)說(shuō)還不夠,粒度更大的包有助于此。但是我們應該怎樣協(xié)調類(lèi)和包之間的從屬關(guān)系?下面的三條原則都屬于包聚合原則,能對我們有所幫助。
3.1 包聚合原則
3.1.1 發(fā)布重用等價(jià)原則The Release Reuse Equivalency Principle (REP)1
重用的粒度就是發(fā)布的粒度。The granule of reuse is the granule of release.
一個(gè)可重用的元件(組件、一個(gè)類(lèi)、一組類(lèi)等),只有在它們被某種發(fā)布(Release)系統管理以后,才能被重用。用戶(hù)不愿意使用那些每次改動(dòng)以后都要被強迫升級的元件。因此,即使開(kāi)發(fā)者發(fā)布了可重用元件的新版本,他也必須支持和維護舊版本,這樣才有時(shí)間讓用戶(hù)熟悉新版本。
因此,將什么類(lèi)放在一個(gè)包中的判斷標準之一就是重用,并且因為包是發(fā)布的最小單元,它們同樣也是重用的最小單元。體系結構師應該將可重用的類(lèi)都放在包中。
3.1.2 共同封閉原則The Common Closure Principle (CCP)2
一起變化的類(lèi)放在一起。Classes that change together, belong together.
一個(gè)大的開(kāi)發(fā)項目通常分割成很多網(wǎng)狀互聯(lián)的包。管理、測試和發(fā)布這些包的工作可不是微不足道的工作。在任何一個(gè)發(fā)布的版本中,如果改動(dòng)的包數量越多,重建、測試和部署也就會(huì )越多。因此我們應該盡量減少在產(chǎn)品的發(fā)布周期中被改動(dòng)的包的數量,這就要求我們將一起變化的類(lèi)放在一起(同一個(gè)包)。
3.1.3 共同重用原則The Common Reuse Principle (CRP)3
不一起重用的類(lèi)不應該放在一起。Classes that aren’t reused together should not be grouped together.
對一個(gè)包的依賴(lài)就是對包里面所有東西的依賴(lài)。當一個(gè)包改變時(shí),這個(gè)包的所有使用者都必須驗證是否還能正常運行,即使它們所用到的沒(méi)有任何改變也不行。
比如我們就經(jīng)常遇到操作系統需要升級。當開(kāi)發(fā)商發(fā)布一個(gè)新版本以后,我們的升級是遲早的問(wèn)題,因為開(kāi)發(fā)商將會(huì )不支持舊版本,即使我們對新版本沒(méi)有任何興趣,我們也得升級。
如果把不一起使用的類(lèi)放在一起,同樣的事情我們也會(huì )遇到。一個(gè)和我們無(wú)關(guān)的類(lèi)的改變也產(chǎn)生包的一個(gè)新版本,我們被強迫升級和驗證這個(gè)包是否影響正常的運行。
3.1.4 包聚合原則之間的張力Tension between the Package Cohesion Principles
這三條原則實(shí)際上是互斥的。它們不能被同時(shí)滿(mǎn)足,因為每一條原則都只針對某一方面,只對某一部分人有好處。REP和CRP都想重用元件的人有好處,CCP對維護人員有好處。CCP使得包有盡可能大的趨勢(畢竟,如果所有的類(lèi)都屬于一個(gè)包,那么將只會(huì )有一個(gè)包變化);CRP盡量使得包更小。
幸運的是,包并不是一成不變的。實(shí)際上,在開(kāi)發(fā)過(guò)程中,包的轉義和增刪都是很正常的。在項目開(kāi)發(fā)的早期,軟件建筑師建立包的結構體系,此時(shí)CCP占主導地位,維護只是輔助。在體系結構穩定以后,軟件建筑師會(huì )對包結構進(jìn)行重構,此時(shí)盡可能的運用REP和CRP,從而最大的方便重用元件的人員。
3.2 包耦合原則The Package Coupling Principles.
下面三條原則主要關(guān)心包之間的關(guān)系。
3.2.1 無(wú)依賴(lài)回路原則The Acyclic Dependencies Principle (ADP)1
包與包之間的依賴(lài)不能形成回路。The dependencies between packages must not form cycles.
因為包是發(fā)布的粒度。人們傾向于節省人力資源,所以工程師們通常只編寫(xiě)一個(gè)包而不是十幾個(gè)包。這種傾向由于包聚合原則被放大,后來(lái)人們就將相關(guān)的類(lèi)組成一組。
因此,工程師發(fā)現他們只會(huì )改動(dòng)較少的幾個(gè)包,一旦這些改動(dòng)完成,他們就可以發(fā)布他們改動(dòng)的包。但是在發(fā)布前,他們必須進(jìn)行測試。為了測試,他們必須編譯和連編他們的包所依賴(lài)的所有的包。
3.2.2 依賴(lài)穩定原則(Stable Dependencies Principle,SDP)
朝穩定的方向依賴(lài)Depend in the direction of stability.
雖然這條原則看起來(lái)很明顯,但是關(guān)于這方面還是有很多需要說(shuō)明的地方,穩定性并不一定為大家所了解。
穩定性是什么?站在一個(gè)硬幣上,這穩定嗎?很可能你說(shuō)不。然而,除非被打擾,硬幣將保持那個(gè)位置很長(cháng)時(shí)間。硬幣沒(méi)有變化,但是很難認為它是穩定的。穩定性與需要改動(dòng)所要做的工作量相關(guān)。硬幣不穩定是因為只需要很小的工作量就能把它翻過(guò)來(lái)。換個(gè)角度,桌子就要穩定得多。
對于軟件這說(shuō)明什么?一個(gè)軟件包很難被改動(dòng)受很多因素影響:代碼大小、復雜度、透明度等等。這些我們先不說(shuō),可以肯定的一點(diǎn)是,如果有很多其它的包依賴(lài)于一個(gè)軟件包,那么該軟件包就難以改動(dòng)。一個(gè)包如果被許多其它包依賴(lài),那么該包是很穩定的,因為這個(gè)包的任何一個(gè)改動(dòng)都可能需要改動(dòng)很多其它的包。
3.2.3 穩定抽象原則( Stable Abstractions Principle ,SAP)
穩定的包應該是抽象包。Stable packages should be abstract packages.
我們可以想象應用程序的包結構應該是一個(gè)互相聯(lián)系的包的集合,其中不穩定的包在頂端,穩定的包在底部,所有的依賴(lài)方向朝下。那些頂端的包是不穩定而且靈活的,但那些底部的包就很難改動(dòng)。這就導致一個(gè)兩難局面:我們想要將包設計為難以改動(dòng)的嗎?
明顯地,難以改動(dòng)的包越多,我們整個(gè)軟件設計的靈活性就越差。但是好像有一點(diǎn)希望解決這個(gè)問(wèn)題,位于依賴(lài)網(wǎng)絡(luò )最底部的高穩定性的包的確難以改動(dòng),但是如果遵從OCP,這樣的包并不難以擴展。





聯(lián)系客服