在哪里儲存那些有用的靜態程序(static utility routies) 一旦你承認對兩種基本的不同類型的類(通用的類和應用特定的類)的邏輯需求,你也只是離開解決另外一個棘手問題僅一步之遙:到哪里儲存那些經常在手邊使用的、但卻是非面向對象的、有用的靜態程序。
當我看到那些在應用特定的類中嵌入完全通用的程序片的時候,我總是感到很沮喪。假設現在有一個電子商務的應用類名叫Customer,包含了如下的方法:
private String surroundedBy(String string, String quote) { return quote + string + quote; }
Customer類的作者在這里包含了一個工具方法來生成字符串,這個方法被標記為surroundedBy(String, String)。該方法申明為私有,大概是因為作者認為這個方法應該是實現類Customer細節中的一部分。又因為這個方法沒有申明為靜態的,顯然地,該方法是故意被申明為一個實例方法的。看上去很不錯,真的嗎?那么這個方法有什么問題呢?
首先,既然該方法被申明為實例方法,那么它為什么沒有依賴于Customer對象的任何的狀態呢(比如:對象的域)?它沒有依賴于對象中的任何域是因為它根本就不需要或者使用任何的域;這個有用的方法只需要它本身的參數來完成它的功能而不需要任何其它的東西。這是它應該作為與類獨立的有用方法的一個明證;換句話說,實際上這個方法根本就不是一個實例方法。
其次,在Customer類應該包含的內容當中這個方法沒有起到合理的作用,所以它不應該簡單的歸屬于Customer類。是不是看上去有些問題了?那么下面該怎么做呢?
正確的方式是,surroundedBy()方法應該屬于一個專門用來處理字符數據類型的類,而不是類似于Customer性質的應用特定的類。而不幸的是,String類本身被申明為final類型,因此不能繼承它創建子類(比如說BetterString類)來安置surroundedBy()方法。一個合理的變通方式是,定義一個新類專門來處理字符數據類型,我們取名為StringUtilities(或者短一些StringUtils,或者更短一些StringKit),然后把surroundedBy()方法改為public static類型的方法,就象這樣:
public class StringKit {
// .. 許多其它的處理字符數據類型的程序片斷
public static String surroundedBy(String string, String quote) { return quote + string + quote; } // .. 許多其它的處理字符數據類型的程序片斷
} // 類StringKit結束
那么我們在執行抽取方法的重構動作后獲得了什么好處呢?
從短期來看,我們獲得了兩個好處:
# 寫了非常好的可重用的(因此也是很有價值的)代碼段,可以在將來不同的項目和應用中重復使用
# 通過消除不必要的方法提升了Customer類的抽象實現(abstraction implementation)
從長期來看,上面的重構技術會帶來其它的效果,甚至很可能是更加重要和有價值的效果:
# 只需寫較少的新代碼(想來的代碼只要調用StringKit.surroundedBy()就可以了)
# 隨著更多的頂層邏輯和結構變得越來越清晰,您系統的整個架構也變得越來越簡單
# 軟件則由于更多的代碼依賴于基礎的構建模塊庫而變得更加的強大,這些模塊將會被更全面的并且比“平鋪”的應用代碼更加頻繁的進行測試。
不幸的是,實際編寫出的代碼往往夾雜了太多的類似surroundedBy()這種方法,很少有Java程序員會去重用那些方法,這是因為:
# 這些方法都是申明在應用特定的代碼中,這些定義也都是不通用的因此也是不可重用的
# 這些方法對于其他希望使用它們的程序員來說甚至是不可見的,因為它們被定義為私有的或者是包范圍(package-scope)的方法
一個方案是系統地辨認然后移動這些放錯地方的可重用方法到相關特定領域的工具類中去。請參看最后面的“靜態工具方法倉庫,一個個人案例”,通過一個例子來看它是如何解決問題的。
動態包層次 因為代碼重構能帶來積極的正面效果,你還應該堅持不懈的準備好包層次的進化(evolving)。想像一下一棵逐漸長大并且成熟的樹:隨著Java類以及接口數量的增長,包結構中葉子和分支的比率也在不斷的增長。無論什么時候當這個比率達到某個極限,出于本能你會試圖釋放分支上的壓力,創建子分支并且將類和接口重新分配到新的分支中去。我總是將每個包中的類和接口數保持在較低的比率下,一般在7-10的范圍內。(比較一下,java.lang中30個的數量或者更多以及java.util中40個的數量或者更多,是否更多取決于具體的API的版本。有沒有對java.util中這么長的可復用類列表感到過窒息(overwhelmed)呢?為每個包保持比較低的類和接口數量可以防止程序員迷失在你的API中)
隨著包分支數量的增長同樣也需要對子包和父包保持一定的比率。如果這個比率達到了極限,那么你也應該本能地重新整理這些子包以減低父包的壓力。將包結構保持一種美學上的平衡(比如,隨著時間的推移,將結構始終保持成類似不規則碎片形的樹結構,始終記住軟件是一門藝術,也是科學)。
到現在我聽到有很大的聲音在喊:“動態包層次如何適應反向兼容類庫(backward-compatible library classes)中的需要呢?”很明顯,這里有一個有趣的沖突:類庫是需要以用戶友好的形式來增長的。幸運的是,用戶對類庫所依賴的主要的是類庫所提供出來的不變的API(比如:容易記憶的類名,精確的方法命名)。[Java中引入(import)的關鍵字......] (Java's import language feature makes changing the source package from which a type hails less of an obstacle than it could be otherwise.)[我嘗試翻譯,但總覺著翻不確切,如果誰能翻譯,請一定補充一下并告訴我,謝謝!shjunsuper@263.net]
在實際情況中,每次把類或接口從一個包移動到另一個包往往為包層次的增長帶來痛苦,你需要修改(bump)類庫的版本號并且在你的發布申明中說明有哪些不合適的命名已經做了更改(比如:就象Sun公司在幾年前處理Swing的包名一樣)。以我的經驗,類庫的使用者更容易接受一些較小的、短期的痛苦,它們可以偶爾在一些重要的申明中對更改做個說明,申明確保公司的類庫結構不會允許惡化到變成一種負擔,反而成為公司的關鍵資產。(現今,許多的Java工具支撐這種動態的包層次,并且使得包名稱和結構的更改盡可能的沒有痛苦)。
包范圍(package-scope)的聲明 與包聲明有著緊密聯系的、也是很少會被Java教授者(包括書本)很好解釋的、同時也可能很少被Java新手所消化吸收的另外一個Java語言特征就是:如何能正確定義類成員的(訪問)范圍(比如:域、方法、構建器,以及內嵌類(從Java1.1版本以后))。
沒有人會對public和private訪問范圍有任何問題。它們之所以能被很好的理解的原因是它們意義明確,因此通常能夠合適的被使用。而對于protected訪問范圍,則經常完全地被誤解;當談到包范圍時,我們已經進入了一個真正可感知的災難區域。
Java設計者錯誤的把包范圍設計為缺省范圍,因此它是關鍵字無關的包聲明(所有的包都應該有關鍵字,沒有的話就看做是缺省的包)。缺乏明顯關鍵字是問題的根源所在:大多數的Java叢書和教程在最開始幾章里的介紹中往往遺了對包范圍相關的語法以及規則的介紹,這也是因為在介紹大多數的經典程序例子的時候正確的包范圍問題并不是很有必要做一強調。
所以我們都被教授使用缺省的包范圍聲明,因為它不需要任何的關鍵字,因此允許我們不用任何的考慮就可以使用它了(又一個老套的懶惰標志...)。
當然,隨著您Java技巧的熟練,您會逐漸認識到為類成員,尤其是域、方法以及構建器正確地設定包范圍是非常重要和必須的。
域的訪問范圍聲明:通常總是會出錯 對于域來說,大約90%的的情況下都是被聲明為私有類型。這是作為面向對象技術重要部分之一,封裝所直接帶來的結果。其次,面向對象陣營中大多數的意見是,在大多數的情況下,對象的組合比對象繼承更為適合,因此受保護的訪問指示符(protected)應該比私有訪問指示符(private)被使用的頻率低的多。你有必要把域聲明為共有的(public)的唯一理由是當你要把一些常量暴露出來的時候,就象下面的情況:
public static final XXXX THE_CONSTANT;
域聲明為包范圍方式應該和聲明非常量域為公有類型應該被視為同樣的不可接受,這是因為這兩種方式都破壞了對象的封裝性,把他們自己的實現細節暴露出來。然而,聲明為包范圍的域在任何類型的Java源碼中都可以看到,比如書、文章、新聞組,最后一個(決不會少見),在產品代碼中。這里給出一個來自核心庫中的例子(Sun的實現):在類javax.swing.ImageIcon中聲明了這樣一個域:
transient int loadStatus = 0;
請注意這個聲明是如何包含了一個隱性的訪問范圍的聲明,然而實際上在包javax.swing中沒有任何的類來訪問ImageIcon對象中的這個域。實際上,因為ImageIcon同時還聲明了一個公有的對該域狀態的訪問控制符:
public int getImageLoadStatus(){ return loadStatus; }
很明顯的是,這個域應該被聲明私有的。
方法和構造器包范圍的聲明 和域包范圍相比較,方法和構造器的包范圍定義更復雜一些。它們兩者都可以被聲明為以下四種訪問范圍:
# 公有的(Public):適用于所有的情況,都可以被訪問 # 受保護的(Protected):只有子類才可以訪問 # 私有的(Private):只有它自己可以訪問 # 友好的(缺省):只有在同一個包下面的(不包含子目錄)可以訪問
明顯地,如果一個子系統或者模塊存在于系統自己的包下面,那么方法和構造器的包范圍的聲明通常是最合理的并且通常也是必須的。
Good things come in small packages 包的聲明通常必須是作為第一行非注釋語句出現在Java代碼中的,然而,在Java語言誕生了7年之后,大多數的程序員并沒有真正的領略到正確應用package關鍵字所帶來的好處的潛力。這個小關鍵字可以讓你通過拆分并模塊化系統的架構來解決整個項目的復雜程度,并且能讓你創建出長期的軟件開發過程框架以實現代碼的重用。這個潛力再漂亮不過了!所以,下次你創建一個新的包的時候,應該更多的更徹底的考慮一下package這個關鍵字的作用。這將是一項明智的、也是長期的投資。
------------------------------------------------------------------------- 靜態工具類倉庫,一個個人的例子
每個程序員都有他自己喜歡的工具(類)集合。我自己經常用到的工具包是我的可信賴的靜態工具類集合。所有下面列出的類都只包含靜態方法,所以它們永遠不會被實例化。這使得它們非常的簡單易用(就象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
以上的列表顯示了一些程序員個人的或者公司范圍內的MiscUtils(類似的)的內容,即所謂的“讓我們把所有東西都扔進去”的工具類,是一個臨時的方法運輸站,這些方法往往不能馬上被歸類。如果你知道這個日漸增長的MiscUtils類可以進行重構,那么你應該敦促這個類的作者按照以上的分類來對它進行拆分。
|