?? 最大化java代碼的可重用性 (轉(zhuǎn)).txt
字號(hào):
作者:jeru
email: jeru@163.net
日期:8/6/2001 5:35:46 PM
最大化JAVA代碼的可重用性
——克服傳統(tǒng)OO方法在重用方面的缺陷
出處: http://www.javaworld.com
mashy 翻譯
摘要:不要放棄編寫可重用代碼的努力!本文介紹了三種對現(xiàn)有代碼進(jìn)行修改以提高其可重用性的方法。
在程序員中似乎存在著一種日益普遍的觀點(diǎn),認(rèn)為重用只是一個(gè)神話?;蛟S是傳統(tǒng)的面向?qū)ο缶幊谭椒ㄖ兴嬖诘牟蛔阍黾恿酥赜玫睦щy。本文介紹了從另外一種不同的途徑使重用成為可能的三個(gè)步驟。
第一步:將功能實(shí)現(xiàn)從類實(shí)例的方法中移出
由于缺乏精確性,類繼承不是非常理想的代碼重用機(jī)制。換句話說,如果不繼承一個(gè)類的數(shù)據(jù)成員和其他的方法,那么你就無法重用這個(gè)類的某個(gè)單獨(dú)的方法。這些額外的不必要的負(fù)擔(dān)使方法重用的代碼變得復(fù)雜。派生類對其父類的依賴性也以入了額外的復(fù)雜性:對父類的改動(dòng)會(huì)對子類造成影響;當(dāng)修改任意一個(gè)類的時(shí)候,我們很難記得清哪個(gè)方法被覆蓋,哪個(gè)沒有;而且被覆蓋的方法是否會(huì)調(diào)用父類中相應(yīng)的方法并不非常清晰地顯現(xiàn)。
任何執(zhí)行單一概念任務(wù)的方法應(yīng)該能夠成為代碼用的首選而獨(dú)立存在。為了達(dá)到這個(gè)目標(biāo),我們必須會(huì)到過程化的編程模式,將代碼從類實(shí)例的方法中移出,形成具有全局可見性的過程。為了提高這種過程的可重用性,過程代碼應(yīng)該象靜態(tài)的通用方法一樣編寫:每個(gè)過程只能使用自己的輸入?yún)?shù),只能調(diào)用其他全局性的過程完成其工作,不能使用任何非本地的變量。這種對外部依賴的簡化降低了過程使用的復(fù)雜性,也增加了在其他地方使用此過程的可能性。當(dāng)然,由于其結(jié)構(gòu)通常會(huì)變得更為清晰,即使拋開重用的目的不談我們也可以從這種代碼的組織方式中受益。
在Java中,方法不能脫離類而單獨(dú)存在。因此,你可以對相關(guān)的過程進(jìn)行組織并使它們成為一個(gè)獨(dú)立的類中的公共靜態(tài)方法。例如,對于如下所示的一個(gè)類:
class Polygon{
…
public int getPerimeter(){…}
public boolean isConvex(){…}
public Boolean containsPoint(Point p){…}
…
}
可以將它改寫成下面的形式:
class Polygon {
…
public int getPerimeter() { return pPolygon.computePerimeter(this);}
public boolean isConvex() { return pPolygon.isConvex(this);}
public boolean containsPoint() { return pPolygon.containsPoint(this, p);}
…
}
在此處,nPolygon應(yīng)該是這個(gè)樣子:
class pPolygon {
static public int computePerimeter(Polygon polygon) {...}
static public boolean isConvex(Polygon polygon) {...}
static public boolean containsPoint(Polygon polygon, Point p) {...}
}
從類的名字pPolygon可以看出,該類所封裝的過程主要與Polygon類型的對象有關(guān)。名字前面的p表示該類的唯一目的是組織公共靜態(tài)過程。在Java中,類的名字以小寫字母開頭不是一種標(biāo)準(zhǔn)的做法,但象pPloygon這樣的類事實(shí)上并不執(zhí)行普通類的功能。也就是說,它并不代表著一類對象,它只是語言本身所需要的用于代碼組織的實(shí)體。
在上面這個(gè)例子中,改動(dòng)代碼的總體影響是使得客戶代碼不必為了重用其功能而從Polygon繼承。Polygon類的功能現(xiàn)在已經(jīng)由pPolygon類以過程為單位提供??蛻舸a只使用自己需要的代碼,無需關(guān)心自身并不需要的功能。
這并不意味著在這種新型的過程化編程模式中,類不服務(wù)于更有用的目的。恰恰相反,類執(zhí)行組織和封裝對象數(shù)據(jù)成員的必要工作。而且它們通過多重接口實(shí)現(xiàn)多態(tài)性的能力也為代碼重用提供了顯著的支持,這將在下一個(gè)步驟中討論。然而,由于將功能實(shí)現(xiàn)包含在實(shí)例方法中無法實(shí)現(xiàn)理想的代碼重用,所以通過類繼承實(shí)現(xiàn)代碼重用和多態(tài)性支持也不應(yīng)成為最佳的技術(shù)選擇。
在一本被廣為閱讀的書《Design Patterns》中曾簡要地提及一種略有不同的技術(shù)。策略模式(Strategy Pattern)提倡將相關(guān)算法的每個(gè)成員封裝在一個(gè)通用的接口下,以便于客戶端代碼可交換地使用其算法。由于一個(gè)算法通常被作為一個(gè)或幾個(gè)獨(dú)立的過程進(jìn)行編碼,這種封裝更注重執(zhí)行單獨(dú)任務(wù)的過程的重用,而不是執(zhí)行多種任務(wù)的、包含代碼和數(shù)據(jù)的對象的重用。這一步驟體現(xiàn)了相同的基本思想。
然而,將一個(gè)算法封裝在一個(gè)接口下意味著將算法作為實(shí)現(xiàn)接口的對象進(jìn)行編碼。這意味著我們?nèi)匀灰蕾囉谝粋€(gè)與所包裝的對象的數(shù)據(jù)和其他方法相耦合的過程,這樣便會(huì)使其復(fù)用變得復(fù)雜。此外還存在這樣一個(gè)問題,每次需要使用這個(gè)算法的時(shí)候都必須實(shí)例化這些對象,這便會(huì)降低程序的性能。值得慶幸的是,設(shè)計(jì)模式提供了針對這兩個(gè)問題的解決方法??梢栽趯Σ呗詫ο筮M(jìn)行編碼時(shí)應(yīng)用享元模式(Flyweight Pattern,譯者注:還存在一種譯法為輕量模式),這樣每個(gè)對象只會(huì)存在一個(gè)被共知共享的實(shí)例(這針對程序性能的問題),而且每個(gè)共享對象在訪問間隔中并不維持狀態(tài)(于是對象將沒有數(shù)據(jù)成員,這針對大多數(shù)的耦合問題)。由此產(chǎn)生的享元--策略模式非常類似于在這一步驟中所提到的將功能實(shí)現(xiàn)封裝在全局可見的、無狀態(tài)的過程中的技術(shù)。(譯者注:以上這兩段文字讀起來可能有些晦澀難解,建議有興趣的讀者參閱文中所提到《設(shè)計(jì)模式》一書,Erich Gamma等著、李英軍等譯、機(jī)械工業(yè)出版社出版。)
第二步:將非原始的輸入?yún)?shù)類型改為接口類型
在面向?qū)ο缶幊讨?,代碼重用的真正基礎(chǔ)在于通過接口參數(shù)類型利用多態(tài)性,而不是通過類繼承,正如Allen Holub在 “Build User Interfaces for Object-Oriented System, Part 2”中所述:
“……你應(yīng)該通過對接口而不是類編程實(shí)現(xiàn)重用。如果一個(gè)方法的所有參數(shù)都是某個(gè)已知接口的引用,這個(gè)接口由一些你所不知道的類實(shí)現(xiàn),那么這個(gè)方法就能夠操作這樣一些對象:當(dāng)編寫方法的代碼時(shí),這些對象的類甚至還不存在。從技術(shù)上講,可重用的是方法,而不是傳遞給方法的對象。”
將Holub所講的方法應(yīng)用于第一步所得到的結(jié)果,只要某塊功能代碼能夠作為一個(gè)全局可見的過程而獨(dú)立存在,你就可以將其每個(gè)類類型(class-type)的輸入?yún)?shù)改為一個(gè)接口類型,這樣便能進(jìn)一步提高其重用的潛力。那么,實(shí)現(xiàn)此接口類型的任何類的對象都可以作為參數(shù)使用,而不僅僅局限于原始類。由此,這個(gè)過程對可能存在的大量的對象類型都成為可用的。
例如,有這樣一個(gè)全局可見的靜態(tài)過程
static public boolean contains(Rectangle rect, int x, int y) {…}
這個(gè)方法用于檢查給定的矩形是否包含某個(gè)給定的點(diǎn)。在這個(gè)例子中,rect參數(shù)的類型可以從Rectangle類改變?yōu)榻涌陬愋?,如下所示?
static public boolean contains(Rectangular rect, int x, int y){…}
Rectangular可以是下面形式的接口:
public interface Rectangular{
Rectangle getBounds();
}
現(xiàn)在,所有可以被描述為矩形的類(也就意味著實(shí)現(xiàn)了Rectangular接口)的對象都可以作為傳遞給pRectangular.contains()的rect參數(shù)。通過放寬所傳遞的參數(shù)類型的限制,我們使方法具有更好的可重用性。
不過,在上面這個(gè)例子中,Rectangular接口的getBounds方法返回一個(gè)Rectangle類型,你可能會(huì)懷疑使用這個(gè)接口是否具有真正的價(jià)值;換句話說,如果我們知道傳入過程的對象會(huì)在被調(diào)用時(shí)返回一個(gè)Rectangle,為什么不直接傳入Rectangle取代接口類型呢?不這樣做的最重要原因與集合有關(guān),假設(shè)有這樣一個(gè)方法:
static public boolean areAnyOverlapping(Collection rects) {…}
這個(gè)方法的目的在于檢查給定集合中的任意矩形對象是否存在重疊。那么,在方法內(nèi)部遍歷集合中的每個(gè)對象時(shí),如果無法將對象造型(cast)成如Rectangular這樣的接口類型,那么將如何能夠訪問對象的矩形區(qū)域呢?唯一的選擇是將對象造型成為其特定的類型(我們直到它有一個(gè)能夠返回rectangle的方法),這意味著方法必須事先知道其所要操作的是什么類型。這恰恰是這一步驟力圖首先要避免的問題!
第三步:選擇低耦合的輸入?yún)?shù)接口類型
完成第二步之后,應(yīng)該選擇什么樣的接口類型來取代給定的類型呢?答案是能夠通過參數(shù)完全描述過程的需求,同時(shí)又具有最少的額外負(fù)擔(dān)的接口類型。參數(shù)對象所要實(shí)現(xiàn)的接口越簡單,其他特定類實(shí)現(xiàn)此接口的機(jī)會(huì)就越大——由此,其對象可以作為參數(shù)使用的類也就越多。通過下面的例子可以很容易地看到這點(diǎn):
static public boolean areOverlapping(Window window1, Window window2) {...}
這個(gè)方法用于檢查兩個(gè)窗口(假定是矩形窗口)是否重疊,如果這個(gè)方法只要求從參數(shù)獲得兩個(gè)窗口的矩形坐標(biāo),那么簡化參數(shù)的類型使其能反映這個(gè)事實(shí)是一種更好的選擇:
static public boolean areOverlapping(Rectangular rect1, Rectangular rect2) {...}
以上的代碼假設(shè)先前的Window類型的對象同樣可以實(shí)現(xiàn)Rectangular接口?,F(xiàn)在對于所有的矩形對象,都可以重用第一個(gè)方法所包含的功能了。
你可能多次體驗(yàn)到當(dāng)一個(gè)接口能夠完全確定需要通過參數(shù)獲哪那些內(nèi)容時(shí),會(huì)存在太多不必要的方法。在這種情況下,應(yīng)該在全局命名空間中定義一個(gè)新的公共接口以供其他可能面臨同一困境的方法重用。
你可能還會(huì)不止一次地發(fā)現(xiàn),在確定需要通過單一過程的一個(gè)參數(shù)獲取哪些內(nèi)容時(shí),最好創(chuàng)建一個(gè)單獨(dú)的接口。你應(yīng)該只為這個(gè)參數(shù)使用此接口。這通常會(huì)在你希望如同C語言中的函數(shù)指針一樣使用參數(shù)的情況下出現(xiàn)。例如下面的過程:
static public void sort(List list, SortComparison comp) {...}
此過程使用參數(shù)所提供的比較對象comp,通過比較給定列表中的所有對象而對其進(jìn)行排序,sort對comp的全部要求是調(diào)用一個(gè)單獨(dú)的方法進(jìn)行比較。因此,SortComparison應(yīng)該是只帶有一個(gè)方法的接口:
public interface SortComparison {
boolean comesBefore(Object a, Object b);
}
這個(gè)接口的唯一目的是為sort提供一個(gè)與其完成任務(wù)所需功能相聯(lián)系的鉤子(hook),因此SortComparison無法在其他地方重用。
結(jié)束語
以上所述的三個(gè)步驟用于現(xiàn)有的、按照相對傳統(tǒng)的面向?qū)ο蠓椒ㄋ帉懙拇a。這些步驟與面向?qū)ο缶幊碳夹g(shù)結(jié)合就形成了一種可以運(yùn)用于今后代碼編寫中的新方法,它可以提高代碼的可重用性和內(nèi)聚性,同時(shí)降低了耦合度及復(fù)雜性。
很顯然,這些步驟無法運(yùn)用于那些在本質(zhì)上就不適合于重用的代碼。這類代碼通常出現(xiàn)在應(yīng)用程序的表示層(presentation layer)。例如程序中用于創(chuàng)建用戶界面的代碼,以及將輸入事件與完成實(shí)際工作的過程相聯(lián)系的控制代碼,都是屬于那種其功能在不同的程序中差別很大的代碼,這種代碼的重用幾乎是不可能的。
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -