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