其實(shí)在看Martin Fowler的重構(gòu)這本書的很長一段時(shí)間里,我一直把它錯當(dāng)成了另外一本書的中文版,那本書的名字就是:John.Wiley.and.Sons.Refactoring.in.Large.Software.Projects.Performing.Complex.Restructurings.Successfully.Jun.2006
以前曾經(jīng)看過這本書,目前還沒有找到中文版。相對于《重構(gòu)--改善既有代碼的設(shè)計(jì)》的側(cè)重實(shí)踐來說,上面這本書理論性更強(qiáng)一些。看講實(shí)踐的書有個(gè)缺點(diǎn),就是你看了不上手做一做的話,等于白看,而理論呢,你可以記住。所以下面穿插一些《Refactoring.in.Large.Software.Projects.Performing.Complex.Restructurings.Successfully》的理論,有很多通過英文字面意思很難理解,這樣我會在網(wǎng)上找些資料,然后抄記在下面:
設(shè)計(jì)原理
正如代碼會“有味”一樣,結(jié)構(gòu)也會“有味”,他們通常會由對于設(shè)計(jì)原理的違反而引起。因此設(shè)計(jì)原理會為我們提供一些有價(jià)值的東西進(jìn)而幫助我們?yōu)樵O(shè)計(jì)去去狐臭。在我們從設(shè)計(jì)中找出違反這些設(shè)計(jì)原理的地方的同時(shí),我們也有了自己的想法來改善我們的設(shè)計(jì)。。下表列出了當(dāng)前流行的設(shè)計(jì)原理:
1、DRY(Don't Repeat Yourself):不要寫重復(fù)的代碼。也可以叫做“一次且僅一次(Once and Only Once)”原理。
2、SCP(Speaking Code Principle):代碼應(yīng)該清楚地表明它自己的意圖。代碼中出現(xiàn)注釋,說明代碼不能清楚地表現(xiàn)自己的意圖。
3、OCP(Open Closed Principle):一個(gè)設(shè)計(jì)單元應(yīng)該向修改開放。這種修改不應(yīng)該導(dǎo)致調(diào)用方無效。繼承是一種能夠讓你達(dá)到此目的的方法。子類可以進(jìn)行一些調(diào)整,而超類保持不變。
4、LSP(Liskov Substitution Principle):個(gè)軟件實(shí)體如果使用的是一個(gè)基類的話,那么一定適用于其子類,而且它根本不能察覺出基類對象和子類對象的區(qū)別。反過來代換是不成立的
5、DIP(Dependency Inversion Principle):等級高的概念不應(yīng)該依賴于等級低的概念(或?qū)崿F(xiàn))。對于從屬關(guān)系來說剛好相反。因?yàn)榈燃壐叩母拍畋绕鸬燃壍偷母拍顏碚f變動的可能性要小。
6、ISP(Interface Segregation Principle):接口隔離原理。接口應(yīng)該盡量的小。他們應(yīng)該僅包含很少的方法,但是這些被放置在同一個(gè)接口中的方法應(yīng)該是緊密相關(guān)的。
7、REP(Reuse/Release Equivalency Principle):可重用的元素必然是已經(jīng)被發(fā)布的元素。
8、
CRP(Common Closure Principle):
包中的所有類對于同一類性質(zhì)的變化應(yīng)該是共同封閉的。 一個(gè)變化若對一個(gè)包產(chǎn)生影響,則將對該報(bào)的所有類產(chǎn)生影響。而對其他的包不構(gòu)成任何影響。
9、
ADP(Acyclic(無環(huán)的)Dependencies Principle):包之間的依賴關(guān)系應(yīng)當(dāng)是無環(huán)的。
10、
SDP(Stable Dependencies Principle):一個(gè)包應(yīng)該依賴于至少像它自己那樣穩(wěn)定的包。
11、
SAP(Stable Abstractions Principle):越穩(wěn)定的包,其抽象程度越高。不穩(wěn)定的包通常是具體化的包。
12、
TDA(Tell,Don't Ask):不要向一個(gè)對象去申請另外一個(gè)對象,而應(yīng)該告訴這個(gè)對象應(yīng)該如何去做。
13、
SOC(Separation Of Concerns):不要在一個(gè)類中包含多個(gè)關(guān)注點(diǎn)。這通常也被叫做“責(zé)任單一原理”。
類關(guān)系圖中的“狐臭”兩個(gè)或多個(gè)類之間的關(guān)系通常包含“使用”和“繼承”兩種。如果我們想在一個(gè)系統(tǒng)中找出“使用”關(guān)系,我們應(yīng)該看一下“靜態(tài)”關(guān)系圖。在系統(tǒng)運(yùn)行時(shí)呈現(xiàn)出來的“使用”關(guān)系構(gòu)成了對象之間的動態(tài)關(guān)系圖。
一、無用類
出自Refactoring.in.Large.Software.Projects.Performing.Complex.Restructurings.Successfully那些不再使用的類會因?yàn)榫哂幸恍╋@而易見的無用功能而增加系統(tǒng)的負(fù)擔(dān)。這些負(fù)擔(dān)包括:程序開發(fā)者需要花費(fèi)大量的時(shí)間來分辨出一個(gè)正確的類,構(gòu)建系統(tǒng)的時(shí)間變得更長,系統(tǒng)變得更加難以理解。
相對于完全無用的類,更多的情況下你會遇到包含部分無用代碼的類。這是因?yàn)橐粋€(gè)類中包含了太多的功能,而在真正使用的時(shí)候,那些功能又被拋棄掉了。我們可以在重構(gòu)的時(shí)候依據(jù)這些功能把類進(jìn)行拆分,通過拆分我們會首先得到無用類。然后進(jìn)一步對它進(jìn)行處理。
不僅僅單單一個(gè)類會成為無用類,有的時(shí)候,處在同一個(gè)關(guān)系圖中的類都回成為無用類(s)。
無用類通常因?yàn)閮煞N原因而出現(xiàn):
1、技術(shù)上提供覆蓋面盡可能廣的支持:一個(gè)開發(fā)者推測一個(gè)類可能最終會被用到,即使沒有跡象表明現(xiàn)實(shí)中有與之對應(yīng)的需求。
2、重構(gòu):通過對系統(tǒng)的修改,以前需要用到的類,現(xiàn)在變得陳舊過期了。
二、樹型依賴關(guān)系
出自Refactoring.in.Large.Software.Projects.Performing.Complex.Restructurings.Successfully樹型依賴關(guān)系圖體現(xiàn)了對于系統(tǒng)的一種分解。樹中的每一個(gè)類都確定的被另外一個(gè)類使用。
鑒于在面向?qū)ο蟮膽?yīng)用程序中,功能分解本身通常會被看作是一種“狐臭”,一種樹型依賴結(jié)構(gòu)像重復(fù)代碼一樣也應(yīng)該被看作是一種“狐臭”。在圖3-6中,類Protocol除了被類Data Storage使用外,不再被別的地方使用。重用沒有發(fā)生。
三、靜態(tài)環(huán)狀依賴關(guān)系
出自Refactoring.in.Large.Software.Projects.Performing.Complex.Restructurings.Successfully兩個(gè)類彼此使用對方是最簡單的環(huán)狀依賴關(guān)系。環(huán)狀依賴關(guān)系中也可以包含多個(gè)類。
環(huán)狀依賴關(guān)系會使環(huán)本身變得更加臃腫。環(huán)所具有的消極影響包括:
a、不可理解性:這些類不能夠被一個(gè)又一個(gè)的單獨(dú)理解,因?yàn)樗鼈儽舜艘揽繉Ψ絹韺?shí)現(xiàn)功能。又或者是,一個(gè)類需要從多個(gè)類中選擇一個(gè)作為理解他們本身的前提。
b、可維護(hù)性:環(huán)狀依賴可能包含嚴(yán)重的并且不可預(yù)測的遞推關(guān)系,進(jìn)而導(dǎo)致修改包含它的系統(tǒng)變得非常困難。
c、可計(jì)劃性:對環(huán)的修改到底會造成什么影響是很難預(yù)期的。估算到底需要多少的工作來完成這個(gè)修改會是一件很困難的事。
d、設(shè)計(jì)的清潔性:因?yàn)樘幵诃h(huán)中的那些類會直接或間接的訪問到處在環(huán)中的任意一個(gè)類,因此,從理論上講,類中的這種關(guān)系太隨意了。如果在這個(gè)環(huán)中又恰好把一個(gè)方法放到錯誤的類中,會導(dǎo)致理解這種設(shè)計(jì)變得更加困難。
e、重用性:這些類必須同時(shí)重用。如果在一個(gè)給定的語境中,只有其中的一個(gè)類所提供的功能被關(guān)注,因?yàn)樘幵诃h(huán)中,這個(gè)類不能被簡單的剝離并使用。
f、可測試性:這些類必須一起被測試。這增加了測試的要求以及堪錯的難度。如果想要獨(dú)立的測試其中的一個(gè)類,就必須使用“偽對象”。
g、異常處理:通常異常會在環(huán)中被“堆積”。如果環(huán)中的一個(gè)方法拋出了異常,他會潛在得影響環(huán)中的其他方法。
h、依賴引入問題:每一個(gè)處于環(huán)中的類同時(shí)又依賴于另一個(gè)處于環(huán)中的類處在環(huán)外的依賴關(guān)系。
顯然,環(huán)中的最大長度越大,代碼的“狐臭”味就越濃。然而,在有些地方,只包含兩個(gè)類的環(huán)卻是一種被推崇的設(shè)計(jì)方案,被應(yīng)用于很多設(shè)計(jì)模式中(如迭代器模式)。
四、顯式依賴
面向?qū)ο笾С址庋b原理以及信息隱藏原理:內(nèi)部的實(shí)現(xiàn)被隱藏于接口之中。客戶端的代碼無須知道任何有關(guān)于API的實(shí)現(xiàn)。另外,接口與實(shí)現(xiàn)類之間存在一些“裂痕”。很多開發(fā)者相信封裝和信息隱藏只會在成員變量被定義為“私有”的時(shí)候才會有效。這當(dāng)然不是事實(shí):在很多系統(tǒng)中都是支持“屬性”的,并且屬性可以被繼承。然而,有這樣一個(gè)事實(shí),在一個(gè)系統(tǒng)中,顯式依賴關(guān)系卻是無法隱藏的。一個(gè)具有公共依賴關(guān)系的系統(tǒng)在被修改的時(shí)候,總會產(chǎn)生這樣那樣的問題,反之,私有的局部的依賴關(guān)系只會有小范圍的影響。
TDA(Tell,don't ask)原理指出了一條正確的途徑:理想情況下,客戶端告訴對象自己打算做什么。既不要通過已有對象獲取一個(gè)新的對象,更不要用那個(gè)新的對象進(jìn)行一些操作。
下面看一個(gè)例子。在這個(gè)例子中,“訂單”有很多的狀態(tài)。我們可以簡單的以打開的訂單和關(guān)閉的訂單來簡單的區(qū)分一下。打開的訂單就是客戶未回款的訂單。現(xiàn)在我們要寫一些代碼,來計(jì)算所有打開的訂單的總額。
如果我們使用一個(gè)方法calculateValueOpenOrders(注意,這個(gè)方法是寫在調(diào)用方的,我們的調(diào)用方的類名定為Foo),而方法體如下。可以看出這個(gè)方法違反了TDA原理:
public float calculateValueOpenOrders(ListOfOrders orders)
{
float totalValue = 0.0f;
for (int i=0; i<orders.getNumber(); i++)
{
Order a = orders.getOrder(i);
if (a.isOpen)
{
totalValue += a.getValue();
}
}
return totalValue;
}
原因就是客戶端的Foo直接使用Orders來獲取其中的一些信息,而不是告訴Orders我要做什么。
出自Refactoring.in.Large.Software.Projects.Performing.Complex.Restructurings.Successfully現(xiàn)在我們做一下修改,把打開的訂單的金額計(jì)算移動到訂單內(nèi)部進(jìn)行。如果訂單是打開的,返回本訂單的金額,反之,返回0。
public class ListOfOrders
{
public float calculateValueOpenOrders()
{
float totalValue = 0.0f;
for (int i=0; i<getNumber(); i++)
{
Order a = getOrder(i);
totalValue += a.getOpenValue();
}
return totalValue;
}
}
public class Order
{
public float getOpenValue()
{
if (isOpen())
{
return getValue();
}
else
{
return 0;
}
}
}
我們可能會因?yàn)樗蠴rder都要返回打開的訂單的金額而感覺不爽。如果你打算多次使用TDA原理,你可能通過增加一個(gè)方法“addOpenValue”來增加類order的柔性。但是,同時(shí)這也意味著類Order會知道另外的一些Order的存在。在這種情況下,我們可能會違反SOC(即單一職責(zé)原理)。我們不應(yīng)該忽略這樣一點(diǎn):在使用一些設(shè)計(jì)原理的時(shí)候,還要兼顧系統(tǒng)的平衡性。在這個(gè)例子中,這個(gè)方法是否應(yīng)該加入取決于它是否真的與這個(gè)類的“領(lǐng)域模型”所匹配。
新的實(shí)現(xiàn)方式不僅僅使代碼更加簡短,而且還包括如下的優(yōu)點(diǎn):
功能有了正確的歸屬。在大多數(shù)情況下,上面第一段代碼中的方法calculateValueOpenOrders通常是被放到UI類中或者是一些Helper類中,而這些類通常有一些奇異的名字(比如OpenOrders Calculator)。這對于這個(gè)方法來說,不是一個(gè)正確的歸屬。
TDA原理確保類的使用應(yīng)該局部化,而不要散步到系統(tǒng)的各個(gè)角落。因此可以簡化一些優(yōu)化方案的實(shí)現(xiàn)。
posted on 2007-07-08 20:31
littlegai 閱讀(401)
評論(0) 編輯 收藏 引用 所屬分類:
我的讀書筆記