標籤:cts 版本 破壞 層級 apt agg 多個 關係 www.
第13條:使類和成員的可訪問性最小化
第一規則很簡單:儘可能地使每個類或者成員不被外界訪問。換句話說。應該使用與你正在編寫的軟體的對應功能相一致的、儘可能最小的存取層級。
對於頂層的(非嵌套的)類和介面,只有兩種可能的存取層級:包級私人的(package-private)和公有的(public)。如果你用public修飾符聲明了頂層類或者介面,那它就是公有。否則,它將是包級私人的。如果類或者介面能夠被做成包級私人的,它就應該被做成包級私人的。通過把類或者介面做成包級私人,它實際上成了這個包的實現的一部分,而不是該包匯出的API的一部分,在以後的發行版本中,可以對它進行修改、替換,或者刪除,而無需擔心會影響到現有的用戶端程式。如果你把它做成公有的,你就有責任永遠支援它,以保持它們的相容性。
如果一個包級私人的頂層類(或者介面)只是在某一個類的內部被用到,就應該考慮使它成為唯一使用它的那個類的私人嵌套類(見第22條)。這樣可以將它的可存取範圍從包中的所有類縮小到了使用它的那個類。然而,降低不必要公有類的可訪問性,比降低包級私人的頂層類的更重要得多: 因為公有類是包的API的一部分,而包級私人的頂層類則已經是這個包的實現的一部分。
對於成員(域、方法、嵌套類和嵌套介面)有四種可能的存取層級,下面按照可訪問性的遞增順序羅列出來:
- 私人的(private)——只有在聲明該成員的頂層類內部才可以訪問這個成員。
- 包級私人的(package-private)——聲明該成員的包內部的任何類都可以訪問這個成員。從技術上講,它被稱為“預設(default)存取層級”,如果沒有為成員指定存取修飾詞,就採用這個存取層級。
- 受保護的(protected)——聲明該成員的類的子類可以訪問這個成員(但有一些限制[JLS, 6.5.2]),並且,聲明該成員的包內部的任何類也可以訪問這個成員。
- 公有的(public)——在任何地方都可以訪問該成員。
包含會有可變域的類並不是安全執行緒的。
如果final域包含可變對象的引用,它便具有非final域的所有缺點。雖然引用本身不能被修改,但是它所引用的對象卻可以被修改——這會導致災難性的後果。
注意,長度非零的數組總是可變的,所以,類具有公有的靜態final數組域,或者返回這種城的存取方法,這幾乎總是錯誤的。
要注意,許多IDE會產生返回指向私人數組域的引用的存取方法,這樣就會產生這個問題。修正這個問題有兩種方法。可以使公有數組變成私人的,井增加一個公有的不可變列表:
另一種方法是,可以使數組變成私人的,井添加一個公有方法,它返回私人數組的一個備份:
除了公有靜態final域的特殊情形之外,公有類都不應該包含公有域。並且要確保公有靜態final域所引用的對象都是不可變的。
第16條: 複合優先於繼承
與方法調用不同的是,繼承打破了封裝性。超類的實現有可能會隨著發行版本的不同而有所變化,如果真的發生了變化,子類可能會遭到破壞,即使它的代碼完全沒有改變。
導致子類脆弱的一個相關的原因是,它們的超類在後續的發行版本中可以獲得新的方法。
如果超類在後續的發行版本中獲得了一個新的方法,井且不幸的是,你給子類提供了一個簽名相同但傳回型別不同的方法,那麼這樣的子類將無法通過編譯。
幸運的是,有一種辦法可以避免前面提到的所有問題。不用擴充現有的類,而是在新的類中增加一個私人域,它引現有類的一個執行個體。這種設計被稱做“複合(composition) " ,因為現有的類變成了新類的一個組件。新類中的每個執行個體方法都可以調用被包含的現有類執行個體中對應的方法,並返回它的結果。這被稱為轉寄(forwarding),新類中的方法被稱為轉寄方法(forwarding method )。這樣得到的類將會非常穩固,它不依賴於現有類的實現細節。
只有當子類真正是超類的子類型(subtype)時,才適合用繼承。換句話說,對於兩個類A和B,只有當兩者之間確實存在“is-a”關係的時候,類B才應該擴充類A。如果答案是否定的,通常情況下,B應該包含A的一個私人執行個體,並且暴露一個較小的、較簡單的API: A本質上不是B的一部分,只是它的實現細節而已。
如果在適合於使用複合的地方使用了繼承,則會不必要地暴露實現細節。
第17條: 要麼為繼承而設計,並提供文檔說明,要麼就禁止繼承
為了繼承而進行的設計不僅僅涉及自用模式的文檔設計。為了使程式員能夠編寫出更加有效子類,而無需承受不必要的痛苦,類必須通過某種形式提供適當的鉤子(hook),以便能夠進入到它的內部工作流程中,這種形式可以是精心選擇的受保護的(protected)方法,也可以是受保護的域,後者比較少見。
對於為了繼承而設計的類,唯一的剛試方法就是編寫於類。
為了允許繼承,類還必須遵守其他一些約束。構造器決不能調用可被覆蓋的方法,無論是直接調用還是間接調用。如果違反了這條規則,很有可能導致程式失敗。超類的構造器在子類的構造器之前運行,所以,子類中覆蓋版本的方法將會在子類的構造器運行之前就先被調用。
這個問題的最佳解決方案是,對於那些並非為了安全地進行子類化而設計和編寫文檔的類,要禁止子類化。有兩種辦法可以禁止子類化。比較容易的辦法是把這個類聲明為final的。另一種辦法是把所有的構造器都變成私人的,或者包級私人的,並增加一些公有的靜態工廠來替代構造器。
第18條: 介面優於抽象類別
- 現有的類可以很容易被更新,以實現新的介面。
- 介面是定義mixin〔混合類型)的理想選擇。
- 介面允許我們構造非階層的類型架構。
雖然介面不允許包含方法的實現,但是,使用介面來定義類型並不妨礙你為程式員提供實現上的協助。通過對你匯出的每個重要介面都提供一個抽象的骨架實現(skeletal implementation )類,把介面和抽象類別的優點結合起來。介面的作用仍然是定義類型,但是骨架實作類別接管了所有與介面實現相關的工作。
實現了這個接門的類可以把對於介面方法的調用,轉寄到個內部私人類的執行個體上,這個內部私人類擴充了骨架實作類別。這種方法被稱作類比多重繼承(simulated multiple inheritance)。關於多重繼承可以參考“參考資料【1】”。
第19條:介面只用於定義類型
常童介面模式是對介面的不良使用。
如果要匯出常量,可以有幾種合理的選擇方案。如果這些常量與某個現有的類或者介面緊密相關,就應該把這些常量添加到這個類或者介面中。如果這些常量最好被看作枚舉類型的成員,就應該用枚舉類型(enum type)(見第30條)來匯出這些常量。否則,應該使用不可執行個體化的工具類(utility class)(見第4條)來匯出這些常量。
第20條:類層次優於標籤類
標籤類:考慮下面這個類,它能夠表示圓形或者矩形:
這種標籤類(tagged class)有著許多缺點。它們中充斥著樣板代碼,包括枚舉聲明、標籤域以及條件陳述式。由於多個實現亂七八槽地擠在了單個類中,破壞了可讀性。記憶體佔用也增加了,因為執行個體承擔著屬於其他風格的不相關的域。
標籤類過於冗長、容易出錯,並且效率低下。標籤類正是類層次的一種簡單的仿效。
為了將標籤類轉變成類層次,首先要為標籤類中的每個方法都定義一個包含抽象方法的抽象類別,這每個方法的行為都依賴於標籤值。接下來,為每種原始標鑒類都定義根類的具體子類。
第21條: 用函數對象表示策略
Java沒有提供函數指標,但是可以用對象引用實現同樣的功能。調用對象上的方法通常是執行該對象(that object)上的某項操作。然而,我們也可能定義這樣一種對象,它的方法執行其他對象(other objects)(這些對象被顯式傳遞給這些方法)上的操作。如果一個類僅僅匯出這樣的一個方法,它的執行個體實際上就等同於一個指向該方法的指標。這樣的執行個體被稱為函數對象(function object)。例如,考慮下面的類:
指向StringLengthCamparator對象的引用可以被當作是一個指向該比較子的“函數指標(function pointer)",可以在任意一對字串上被調用。換句話說,StringLengthCamparator執行個體是用於字串比較操作的具體策略(concrete strategy)。
作為典型的具體策略類,StringLengthCamparator類是無狀態的(stateless):它沒有域,所以。這個類的所有執行個體在功能上都是相互等價的。因此,它作為一個Singleton是非常合適的,可以節省不必要的對象建立開銷(見第3條和第5條):
簡而言之,函數指標的主要用途就是實現策略( Strategy )模式。為了在Java中實現這種模式,要聲明一個介面來表示該策略,並且為每個具體策略聲明一個實現了該介面的類。當一個具體策略只被使用一次時,通常使用匿名類來聲明和執行個體化這個具體策略類。當一個具體策略是設計用來重複使用的時候。它的類通常就要被實現為私人的靜態成員類,並通過公有的靜態final域被匯出,其類型為該策略介面。
第22條: 優先考慮靜態成員類
非靜態成員類的每個執行個體都隱含著與外圍類的一個外圍執行個體(enclosing instance)相關聯。
作靜態成員類的一種常見用法是定義一個Adapter[Gamma95, p.139],它允許外部類的執行個體被看作是另一個不相關的類的執行個體。例如,Map介面的實現往往使用非靜態成員類來實現它們的集合視圖(collection view),這些集合視圖是由Map的keySet, entrySet和Values方法返回的。同樣地,諸如Set和List這種集合介面的實現往往也使用非靜態成員類來實現它們的迭代器(iterator):
私人靜態成員類的一種常見用法是用來代表外圍類所代表的對象的組件。例如,考慮一個Map執行個體,它把鍵(key)和值(value)關聯起來。許多Map實現的內部都有一個Entry對象,對應於Map中的每個索引值對。雖然每個entry都與一個Map關聯,但是entry上的方法(getKey,getValue和setValue)並不需要訪問該Map。因此,使用非靜態成員來表示entry是很浪費的:私人的靜態成員類是最佳的選擇。如果不小心漏掉了entry聲明中的static修飾符,該Map仍然可以工作,但是每個entry中將會包含一個指向該Map的引用,這樣就浪費了空間和時間。
由於匿名類出現在運算式當中,它們必須保持簡短—大約10行或者更少些——否則會影響程式的可讀性。
匿名類的一種常見用法是動態地建立函數時象(funCtivn object,見第21t條)
匿名類的另一種常見用法是建立過程對象(process object),比如Runnable, Thread或者TimerTask執行個體。
第三種常見的用法是在靜態Factory 方法的內部。
參考資料
【1】java提高篇(九)—–實現多重繼承 http://www.cnblogs.com/chenssy/p/3389027.html
《Effective Java》第4章 類和介面