設計抽象類或接口時需要注意的地方

1、要麼爲繼承而設計,並提供文檔說明,要麼就禁止繼承

首先,該類的文檔必須精確地描述覆蓋每個方法所帶來的影響。換句話說,該類必須有文檔說明它可覆蓋的方法和自用性。對於 每個公有的或受保護的方法或者構造器,它的文檔必須指明該方法或者構造器調用了哪些可覆蓋的方法,是以什麼順序調用的,每個調用的結果又是如何影響後續的處理過程的(所謂可覆蓋的方法是指非final的,僅有的或受保護的)。更一般地,類必須在文檔中說明,在哪些情況下它會調用可覆蓋的方法。

在發佈類之前,必須先編寫子類對類進行測試

爲了允許繼承,類還必須遵守其它一些約束。構造器決不能調用可被覆蓋的方法,無論是直接調用還是間接調用。

無論是clone方法還是readObject,都不可以調用可覆蓋的方法不管是以直接還是間接的方式。

對於那些並非爲了安全地進行子類化而設計和編寫文檔類,要禁止子類化。有兩種方式可以禁止子類化,一是用final修飾類,二是但構造器設計爲私有的,並設計一些公有的靜態的構造工廠來代替工廠類。

如果具體的類沒有實現標準的接口,那麼禁繼承可能會給有些程序員帶來不便,如果你認爲必須允許從這樣的類繼承,一種合理的辦法是確保這個類永遠不會調用它的任何可覆蓋的方法,並在文檔中說明這一點。換句話說,完全消除這個類中可覆蓋方法的自用特性。這樣做之後,就可以創建能夠安全地進行子類化“的類。覆蓋方法將永遠也不會影響其它任何方法的行爲。

你可以機械地調用可以覆蓋方法的自用特性,而不改變它的行爲。將每個可覆蓋方法的代碼體移到一個私有的”輔助方法“中,並且讓每個可覆蓋的方法調用 它的私有輔助方法。然後,用”直接調用可覆蓋方法的私有輔助方法“來代替”可覆蓋方法的每個自用調用“


2、接口優於抽象類

現有類可以很容易被更新,以實現新的接口

接口是定義mixin的理想選擇

接口允許我們構造非層次結構的類型框架

雖然接口不允許包含方法的實現,但是,使用接口來定義類型並不妨礙你爲程序提供實現上的幫助。通過對你導出的每個重要接口 都提供一個抽象的骨架實現類,把接口和抽象類的優點結合起來。接口 的作用仍然是定義類型,但是骨架實現類接管了所有與接口實現相關的工作。

骨架實現的美妙之處在於,它們爲抽象類提供了實現上的幫助,但又不強加”抽象類被用作類型定義時“所持有的嚴格限制。對於接口的大多數實現來講,擴展骨架實現類是個很顯然的選擇,但並不是必需的。

一般來說,要想在僅有接口中增加方法,而不破壞實現這個接口所有實現的類,這是不可能的。因此,在設計公有接口要非常謹慎。接口一但被公開,並且已被廣泛使用,再想改變這個接口幾乎是不可能的。

簡而言之,接口通常是定義允許多個實現的類型的最佳途徑。這條規則有個例外,即當演變的容易性比靈活性和功能更爲重要的時候。在這種情況下,應該使用抽象類來定義類型,但前提是必須理解並且可以接受這些侷限性。如果你導出了一個重要的接口,就應該堅決考慮同時提供骨架實現類。最後 ,應該儘可能地謹慎地設計 所有 的公有接口,並且通過編寫多個實現來對它們進行全面的測試。

3、接口只用於定義類型

有一種接口 被 稱爲常量接口,它不滿足上面的條件。這種接口沒有包含任何方法,它只包含靜態的final域,每個域都導出一個常量。使用這些常量的類實現這個接口,又避免用類名來修飾常量名。

常量接口模式是對接口 的不良使用。類在內部使用某些常量這純粹是實現細節。實現常量接口 ,會導致把這樣的實現細節泄露到該類的導出API中。類實現常量接口,這對於這個類的用戶來講並沒有什麼價值。實際上,這樣做反而會使他們更加糊塗。更糟糕的是,它代表了一種承諾:如果在將來的發行版本中,這個類被修改了,它不再需要使用這些常量了,依然必須實現這個接口,又確保二進制兼容性。如果非final類實現了常量接口,它的所有子類的命名空間也會被接口中的常量所”污染“

簡而言之,接口應該被用來定義類型,而不應該被用來導出常量。


4、類層次優於標籤類

標籤類有着許多的缺點。它們中充斥着樣板代碼,包括枚舉聲明、標籤域以及條件語句。由於 多個實現亂七八糟地擠在了單個類中,破壞了可讀性。內存佔用增加了,因爲實例承擔着屬於其他風格的不相關的域,產生更多的樣板代碼。總之:標籤類過於冗長,容易出錯並且效率低下。

層次化,子類化,每個類型的實現都配有自己的類,這些類都沒有受到不相關的數據域的拖累。所有 的域都是final的。編譯器確保每個類的構造器都初始化它的數據域,對於根類中聲明的每個抽象方法,都確保有一個實現。這樣就杜絕了由於 遺漏switch case而導致運行時失敗的可能性。多個程序員可以獨立地擴展層次結構,並且不用訪問源代碼就能相互操作。每種類型都有一種相關的獨立的數據類型,允許程序員指明變量的類型,限制變量,並將參數輸入到特殊的類型。

類層次的另一種好處在於,它們可以用來反映類型之間本質上的層次關係,有助於增強靈活性,並進行更好的編譯時類型檢查。

簡而言之:標籤類很少有適用的時候。當你想要編寫一個包含顯示標籤域的類時,應該考慮一下,這個標籤是否可以被取消,這個類是否可以用層次來代替。當你遇到 一個包含標籤域的現有類時,就要考慮將它重構到一個層次結構中去。


5、用函數對象表示策略

函數指針的主要用途主要是實現策略模式。爲了在Java中實現這種模式,要聲明一個接口 來表示該策略,並且爲每個具體策略聲明一個實現了該接口的類。當一個具體策略只被使用一次時,通常使用匿名類來聲明和實例 化這個具體策略類,當一個具體策略是設計用來重複使用的時候,它的類通常就要被實現爲私有的靜態成員類,並通過公有的表態final域被導出,其類型爲該策略接口。(不理解這段的意思)


6、優先考慮靜態成員類

嵌套類:是指被定義在另一個類的內部的類。嵌套類存在的目的應該只是爲它的外圍類提供服務。如果嵌套類將來可能會用於其他的某個環境中,它就應該是頂層類。嵌套類有四種:靜態成員類、非靜態成員類、匿名類和局部類。除了第一種外,其它三種都被稱爲內部類。

靜態成員類是最簡單的一種嵌套類。最好把它看作是普通的類,只是碰巧被聲明在另一個類的內部而已,它可以訪問外圍類的所有成員,包括那些聲明爲私有的成員。靜態成員類是外圍類的一個靜態成員,與其他的靜態成員一樣,也遵守同樣的可訪問性規則。如果它被聲明爲私有的,它就只能在外圍類的內部纔可以被訪問

從語法上講,靜態成員類和非靜態成員類之間的唯一區別是,靜態成員類的聲明中包含修飾符static。儘管它們的語法非常相似,但是這兩種嵌套類有很大的區別。非靜態成員類的每個實例都隱含着與外圍類的一個外圍實例相相關聯。在非靜態成員類的實例方法內部,可以調用外圍實例上的方法,或者利用修飾過的this構造獲得外圍實例的引用。如果嵌套類的實例可以在它的實例之外獨立存在,這個嵌套類就必須是靜態成員類:在沒有外圍實例的情況下,要想創建非靜態成員類的實例是不可能的。

當非表態成員類的實例被創建的時候,它和外圍實例之間的關係也隨之被創建起來。而且,這種關聯關係以後都不能被修改。

非靜態成員類的一種常見用法是定義一個Adapter,它允許外部類的實例被看作是另一個不相關的類的實例。

如果聲明成員類不要求訪問外圍實例,就要始終把static修飾符放在它的聲明中,使它成爲表態成員類,而不是非靜態成員類。如果省略了static修飾符,則每個實例都將包含一個額外的指向對象的引用。保存這份引用要消耗時間和空間,並且會導致外圍實例在符合垃圾回收時去卻仍然得以保留。如果在沒有外圍實例的情況下,也需要分配實例,就不能使用非靜態成員類,因爲非靜態成員類的實例必須要有一個外圍實例。

私有靜態成員的一種常見用法是用來代表外圍類日所代表的對象的組件。

如果相關的類是導出來類的僅有的或受保護的成員,毫不疑問,在靜態和非靜態成員類之間做出正確的選擇是非常重要的。

匿名類的適用性受到諸多的限制。除了在它們被聲明的時候之外,是無法將它們實例化的。你不能執行instanceof測試,或者做任何需要命名類的其他事情。你無法聲明一個匿名類來實現多個接口,或者擴展一個類,並同時擴展類和實現接口。匿名類的客戶端無法調用任何成員,除了從它的超類型中繼承得到之外。由於匿名類出現在表達式中,它們必須保持簡短-------大約10行或者更少些--------否則會影響程序的可讀性。

匿名類的一種常見的用法是動態地創建函數對象。


摘抄自:Effective Java

發佈了25 篇原創文章 · 獲贊 5 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章