在哪里儲(chǔ)存那些有用的靜態(tài)程序(static utility routies) 一旦你承認(rèn)對(duì)兩種基本的不同類型的類(通用的類和應(yīng)用特定的類)的邏輯需求,你也只是離開解決另外一個(gè)棘手問題僅一步之遙:到哪里儲(chǔ)存那些經(jīng)常在手邊使用的、但卻是非面向?qū)ο蟮、有用的靜態(tài)程序。
當(dāng)我看到那些在應(yīng)用特定的類中嵌入完全通用的程序片的時(shí)候,我總是感到很沮喪。假設(shè)現(xiàn)在有一個(gè)電子商務(wù)的應(yīng)用類名叫Customer,包含了如下的方法:
private String surroundedBy(String string, String quote) { return quote + string + quote; }
Customer類的作者在這里包含了一個(gè)工具方法來生成字符串,這個(gè)方法被標(biāo)記為surroundedBy(String, String)。該方法申明為私有,大概是因?yàn)樽髡哒J(rèn)為這個(gè)方法應(yīng)該是實(shí)現(xiàn)類Customer細(xì)節(jié)中的一部分。又因?yàn)檫@個(gè)方法沒有申明為靜態(tài)的,顯然地,該方法是故意被申明為一個(gè)實(shí)例方法的?瓷先ズ懿诲e(cuò),真的嗎?那么這個(gè)方法有什么問題呢?
首先,既然該方法被申明為實(shí)例方法,那么它為什么沒有依賴于Customer對(duì)象的任何的狀態(tài)呢(比如:對(duì)象的域)?它沒有依賴于對(duì)象中的任何域是因?yàn)樗揪筒恍枰蛘呤褂萌魏蔚挠;這個(gè)有用的方法只需要它本身的參數(shù)來完成它的功能而不需要任何其它的東西。這是它應(yīng)該作為與類獨(dú)立的有用方法的一個(gè)明證;換句話說,實(shí)際上這個(gè)方法根本就不是一個(gè)實(shí)例方法。
其次,在Customer類應(yīng)該包含的內(nèi)容當(dāng)中這個(gè)方法沒有起到合理的作用,所以它不應(yīng)該簡單的歸屬于Customer類。是不是看上去有些問題了?那么下面該怎么做呢?
正確的方式是,surroundedBy()方法應(yīng)該屬于一個(gè)專門用來處理字符數(shù)據(jù)類型的類,而不是類似于Customer性質(zhì)的應(yīng)用特定的類。而不幸的是,String類本身被申明為final類型,因此不能繼承它創(chuàng)建子類(比如說BetterString類)來安置surroundedBy()方法。一個(gè)合理的變通方式是,定義一個(gè)新類專門來處理字符數(shù)據(jù)類型,我們?nèi)∶麨镾tringUtilities(或者短一些StringUtils,或者更短一些StringKit),然后把surroundedBy()方法改為public static類型的方法,就象這樣:
public class StringKit {
// .. 許多其它的處理字符數(shù)據(jù)類型的程序片斷
public static String surroundedBy(String string, String quote) { return quote + string + quote; } // .. 許多其它的處理字符數(shù)據(jù)類型的程序片斷
} // 類StringKit結(jié)束
那么我們?cè)趫?zhí)行抽取方法的重構(gòu)動(dòng)作后獲得了什么好處呢?
從短期來看,我們獲得了兩個(gè)好處:
# 寫了非常好的可重用的(因此也是很有價(jià)值的)代碼段,可以在將來不同的項(xiàng)目和應(yīng)用中重復(fù)使用
# 通過消除不必要的方法提升了Customer類的抽象實(shí)現(xiàn)(abstraction implementation)
從長期來看,上面的重構(gòu)技術(shù)會(huì)帶來其它的效果,甚至很可能是更加重要和有價(jià)值的效果:
# 只需寫較少的新代碼(想來的代碼只要調(diào)用StringKit.surroundedBy()就可以了)
# 隨著更多的頂層邏輯和結(jié)構(gòu)變得越來越清晰,您系統(tǒng)的整個(gè)架構(gòu)也變得越來越簡單
# 軟件則由于更多的代碼依賴于基礎(chǔ)的構(gòu)建模塊庫而變得更加的強(qiáng)大,這些模塊將會(huì)被更全面的并且比“平鋪”的應(yīng)用代碼更加頻繁的進(jìn)行測(cè)試。
不幸的是,實(shí)際編寫出的代碼往往夾雜了太多的類似surroundedBy()這種方法,很少有Java程序員會(huì)去重用那些方法,這是因?yàn)椋?br> # 這些方法都是申明在應(yīng)用特定的代碼中,這些定義也都是不通用的因此也是不可重用的
# 這些方法對(duì)于其他希望使用它們的程序員來說甚至是不可見的,因?yàn)樗鼈儽欢x為私有的或者是包范圍(package-scope)的方法
一個(gè)方案是系統(tǒng)地辨認(rèn)然后移動(dòng)這些放錯(cuò)地方的可重用方法到相關(guān)特定領(lǐng)域的工具類中去。請(qǐng)參看最后面的“靜態(tài)工具方法倉庫,一個(gè)個(gè)人案例”,通過一個(gè)例子來看它是如何解決問題的。
動(dòng)態(tài)包層次 因?yàn)榇a重構(gòu)能帶來積極的正面效果,你還應(yīng)該堅(jiān)持不懈的準(zhǔn)備好包層次的進(jìn)化(evolving)。想像一下一棵逐漸長大并且成熟的樹:隨著Java類以及接口數(shù)量的增長,包結(jié)構(gòu)中葉子和分支的比率也在不斷的增長。無論什么時(shí)候當(dāng)這個(gè)比率達(dá)到某個(gè)極限,出于本能你會(huì)試圖釋放分支上的壓力,創(chuàng)建子分支并且將類和接口重新分配到新的分支中去。我總是將每個(gè)包中的類和接口數(shù)保持在較低的比率下,一般在7-10的范圍內(nèi)。(比較一下,java.lang中30個(gè)的數(shù)量或者更多以及java.util中40個(gè)的數(shù)量或者更多,是否更多取決于具體的API的版本。有沒有對(duì)java.util中這么長的可復(fù)用類列表感到過窒息(overwhelmed)呢?為每個(gè)包保持比較低的類和接口數(shù)量可以防止程序員迷失在你的API中)
隨著包分支數(shù)量的增長同樣也需要對(duì)子包和父包保持一定的比率。如果這個(gè)比率達(dá)到了極限,那么你也應(yīng)該本能地重新整理這些子包以減低父包的壓力。將包結(jié)構(gòu)保持一種美學(xué)上的平衡(比如,隨著時(shí)間的推移,將結(jié)構(gòu)始終保持成類似不規(guī)則碎片形的樹結(jié)構(gòu),始終記住軟件是一門藝術(shù),也是科學(xué))。
到現(xiàn)在我聽到有很大的聲音在喊:“動(dòng)態(tài)包層次如何適應(yīng)反向兼容類庫(backward-compatible library classes)中的需要呢?”很明顯,這里有一個(gè)有趣的沖突:類庫是需要以用戶友好的形式來增長的。幸運(yùn)的是,用戶對(duì)類庫所依賴的主要的是類庫所提供出來的不變的API(比如:容易記憶的類名,精確的方法命名)。[Java中引入(import)的關(guān)鍵字......] (Java's import language feature makes changing the source package from which a type hails less of an obstacle than it could be otherwise.)[我嘗試翻譯,但總覺著翻不確切,如果誰能翻譯,請(qǐng)一定補(bǔ)充一下并告訴我,謝謝!shjunsuper@263.net]
在實(shí)際情況中,每次把類或接口從一個(gè)包移動(dòng)到另一個(gè)包往往為包層次的增長帶來痛苦,你需要修改(bump)類庫的版本號(hào)并且在你的發(fā)布申明中說明有哪些不合適的命名已經(jīng)做了更改(比如:就象Sun公司在幾年前處理Swing的包名一樣)。以我的經(jīng)驗(yàn),類庫的使用者更容易接受一些較小的、短期的痛苦,它們可以偶爾在一些重要的申明中對(duì)更改做個(gè)說明,申明確保公司的類庫結(jié)構(gòu)不會(huì)允許惡化到變成一種負(fù)擔(dān),反而成為公司的關(guān)鍵資產(chǎn)。(現(xiàn)今,許多的Java工具支撐這種動(dòng)態(tài)的包層次,并且使得包名稱和結(jié)構(gòu)的更改盡可能的沒有痛苦)。
包范圍(package-scope)的聲明 與包聲明有著緊密聯(lián)系的、也是很少會(huì)被Java教授者(包括書本)很好解釋的、同時(shí)也可能很少被Java新手所消化吸收的另外一個(gè)Java語言特征就是:如何能正確定義類成員的(訪問)范圍(比如:域、方法、構(gòu)建器,以及內(nèi)嵌類(從Java1.1版本以后))。
沒有人會(huì)對(duì)public和private訪問范圍有任何問題。它們之所以能被很好的理解的原因是它們意義明確,因此通常能夠合適的被使用。而對(duì)于protected訪問范圍,則經(jīng)常完全地被誤解;當(dāng)談到包范圍時(shí),我們已經(jīng)進(jìn)入了一個(gè)真正可感知的災(zāi)難區(qū)域。
Java設(shè)計(jì)者錯(cuò)誤的把包范圍設(shè)計(jì)為缺省范圍,因此它是關(guān)鍵字無關(guān)的包聲明(所有的包都應(yīng)該有關(guān)鍵字,沒有的話就看做是缺省的包)。缺乏明顯關(guān)鍵字是問題的根源所在:大多數(shù)的Java叢書和教程在最開始幾章里的介紹中往往遺了對(duì)包范圍相關(guān)的語法以及規(guī)則的介紹,這也是因?yàn)樵诮榻B大多數(shù)的經(jīng)典程序例子的時(shí)候正確的包范圍問題并不是很有必要做一強(qiáng)調(diào)。
所以我們都被教授使用缺省的包范圍聲明,因?yàn)樗恍枰魏蔚年P(guān)鍵字,因此允許我們不用任何的考慮就可以使用它了(又一個(gè)老套的懶惰標(biāo)志...)。
當(dāng)然,隨著您Java技巧的熟練,您會(huì)逐漸認(rèn)識(shí)到為類成員,尤其是域、方法以及構(gòu)建器正確地設(shè)定包范圍是非常重要和必須的。
域的訪問范圍聲明:通?偸菚(huì)出錯(cuò) 對(duì)于域來說,大約90%的的情況下都是被聲明為私有類型。這是作為面向?qū)ο蠹夹g(shù)重要部分之一,封裝所直接帶來的結(jié)果。其次,面向?qū)ο箨嚑I中大多數(shù)的意見是,在大多數(shù)的情況下,對(duì)象的組合比對(duì)象繼承更為適合,因此受保護(hù)的訪問指示符(protected)應(yīng)該比私有訪問指示符(private)被使用的頻率低的多。你有必要把域聲明為共有的(public)的唯一理由是當(dāng)你要把一些常量暴露出來的時(shí)候,就象下面的情況:
public static final XXXX THE_CONSTANT;
域聲明為包范圍方式應(yīng)該和聲明非常量域?yàn)楣蓄愋蛻?yīng)該被視為同樣的不可接受,這是因?yàn)檫@兩種方式都破壞了對(duì)象的封裝性,把他們自己的實(shí)現(xiàn)細(xì)節(jié)暴露出來。然而,聲明為包范圍的域在任何類型的Java源碼中都可以看到,比如書、文章、新聞組,最后一個(gè)(決不會(huì)少見),在產(chǎn)品代碼中。這里給出一個(gè)來自核心庫中的例子(Sun的實(shí)現(xiàn)):在類javax.swing.ImageIcon中聲明了這樣一個(gè)域:
transient int loadStatus = 0;
請(qǐng)注意這個(gè)聲明是如何包含了一個(gè)隱性的訪問范圍的聲明,然而實(shí)際上在包javax.swing中沒有任何的類來訪問ImageIcon對(duì)象中的這個(gè)域。實(shí)際上,因?yàn)镮mageIcon同時(shí)還聲明了一個(gè)公有的對(duì)該域狀態(tài)的訪問控制符:
public int getImageLoadStatus(){ return loadStatus; }
很明顯的是,這個(gè)域應(yīng)該被聲明私有的。
方法和構(gòu)造器包范圍的聲明 和域包范圍相比較,方法和構(gòu)造器的包范圍定義更復(fù)雜一些。它們兩者都可以被聲明為以下四種訪問范圍:
# 公有的(Public):適用于所有的情況,都可以被訪問 # 受保護(hù)的(Protected):只有子類才可以訪問 # 私有的(Private):只有它自己可以訪問 # 友好的(缺省):只有在同一個(gè)包下面的(不包含子目錄)可以訪問
明顯地,如果一個(gè)子系統(tǒng)或者模塊存在于系統(tǒng)自己的包下面,那么方法和構(gòu)造器的包范圍的聲明通常是最合理的并且通常也是必須的。
Good things come in small packages 包的聲明通常必須是作為第一行非注釋語句出現(xiàn)在Java代碼中的,然而,在Java語言誕生了7年之后,大多數(shù)的程序員并沒有真正的領(lǐng)略到正確應(yīng)用package關(guān)鍵字所帶來的好處的潛力。這個(gè)小關(guān)鍵字可以讓你通過拆分并模塊化系統(tǒng)的架構(gòu)來解決整個(gè)項(xiàng)目的復(fù)雜程度,并且能讓你創(chuàng)建出長期的軟件開發(fā)過程框架以實(shí)現(xiàn)代碼的重用。這個(gè)潛力再漂亮不過了!所以,下次你創(chuàng)建一個(gè)新的包的時(shí)候,應(yīng)該更多的更徹底的考慮一下package這個(gè)關(guān)鍵字的作用。這將是一項(xiàng)明智的、也是長期的投資。
------------------------------------------------------------------------- 靜態(tài)工具類倉庫,一個(gè)個(gè)人的例子
每個(gè)程序員都有他自己喜歡的工具(類)集合。我自己經(jīng)常用到的工具包是我的可信賴的靜態(tài)工具類集合。所有下面列出的類都只包含靜態(tài)方法,所以它們永遠(yuǎn)不會(huì)被實(shí)例化。這使得它們非常的簡單易用(就象java.lang.Math一樣)。
類名位于包
AppKitorg.lv.lego ArrayKitorg.lv.lego BeansKitorg.lv.lego.beans CheckboxKit org.lv.lego.gui ChoiceKit org.lv.lego.gui CLIKitorg.lv.lego ColorKitorg.lv.lego.graphics CombinatoricsKitorg.lv.lego.math ConvertKitorg.lv.lego.math DatabaseKit org.lv.lego.database DialogKit org.lv.lego.gui EncryptKitorg.lv.lego.math FileKit org.lv.lego.files GeometryKit org.lv.lego.math GfxKitorg.lv.lego.graphics GUIKitorg.lv.lego.gui HTMLKit org.lv.lego.html ImageKitorg.lv.lego.image JavaKit org.lv.lego.java JVMKitorg.lv.lego.java.jvm ListKit org.lv.lego.gui
LowLevelKit org.lv.lego MathKit org.lv.lego.math MenuKit org.lv.lego.gui MiscKit org.lv.lego NetKitorg.lv.lego.comms.net PersistencyKitorg.lv.lego PrimeKitorg.lv.lego.math RandomKit org.lv.lego.math ReflectionKit org.lv.lego.java RuntimeKitorg.lv.lego.java StatsKitorg.lv.lego.math StreamsKitorg.lv.lego.streams StringKit org.lv.lego.text SwingDialogKitorg.lv.lego.gui.swing TableKitorg.lv.lego.gui.swing TextComponentKitorg.lv.lego.gui ThreadKit org.lv.lego.threads TimeAndDateKitorg.lv.lego UDPKitorg.lv.lego.comms.net WebKitorg.lv.lego.comms.net ZipKitorg.lv.lego.crunch
以上的列表顯示了一些程序員個(gè)人的或者公司范圍內(nèi)的MiscUtils(類似的)的內(nèi)容,即所謂的“讓我們把所有東西都扔進(jìn)去”的工具類,是一個(gè)臨時(shí)的方法運(yùn)輸站,這些方法往往不能馬上被歸類。如果你知道這個(gè)日漸增長的MiscUtils類可以進(jìn)行重構(gòu),那么你應(yīng)該敦促這個(gè)類的作者按照以上的分類來對(duì)它進(jìn)行拆分。
|
溫馨提示:喜歡本站的話,請(qǐng)收藏一下本站!