3 抽象類和接口
抽象類仍然是一個類,在建立新類時隻能繼承它一個。而建立類的過程中可以實作多個接口。
盡可能地抽象
是以,更傾向使用接口而不是抽象類。
隻有當必要時才使用抽象類。
除非必須使用,否則不要用接口和抽象類。
大多數時候,普通類已經做得很好,如果不行的話,再移動到接口或抽象類中。
4 完全解耦
當方法操縱的是一個類而非接口時,它就隻能作用于那個類或其子類。如果想把方法應用于那個繼承層級結構之外的類,就會觸黴頭。接口在很大程度上放寬了這個限制,将接口與實作解耦使得接口可以應用于多種不同的實作,因而代碼更具可複用性。
5 多接口組合
接口沒有任何實作——也就是說,沒有任何與接口相關的存儲——是以無法阻止組合多接口。這是有價值的,因為你有時需要表示“一個 x 是一個 a 和一個 b 以及一個 c”。
子類并不要求必須繼承自abstract的或“具體的”(沒有任何抽象方法)的父類。如果繼承一個非接口類,那麼隻能繼承一個類,其餘的基元素必須都是接口。需要将所有的接口名稱置于 implements 關鍵字之後且用逗号分隔。可以有任意多個接口,并可以向上轉型為每個接口,因為每個接口都是獨立的類型。
類結合了具體類和接口。當通過這種方式結合具體類和接口時,需要将具體類放在前面,後面跟着接口(否則編譯器會報錯)。
使用接口的核心原因之一:為了能夠向上轉型為多個基類型(以及由此帶來的靈活性)。
然而,使用接口的第二個原因與使用抽象基類相同:防止用戶端程式員建立這個類的對象,確定這僅僅隻是一個接口。
這帶來了一個問題:應該使用接口還是抽象類呢?
如果建立不帶任何方法定義或成員變量的基類,就選擇接口而不是抽象類。事實上,如果知道某事物是一個基類,可以考慮用接口實作它。
6 使用繼承擴充接口
通過繼承,可以很容易
- 在接口中增加方法聲明
- 在新接口中結合多個接口
這兩種情況都可以得到新接口。
6.1 多接口時的命名沖突
當實作多個接口時可能會存在一個小陷阱。之前說到完全相同的方法沒有問題,但是如果它們的簽名或傳回類型不同會怎麼樣呢?
覆寫、實作和重載會令人不快地攪和在一起、。同時,重載方法僅根據傳回類型是區分不了的。是以,當打算組合接口時,在不同的接口中使用相同的方法名通常會造成代碼可讀性的混亂,盡量避免這種情況。
7 接口适配
接口最吸引人的原因之一是相同的接口可以有多個實作。
在簡單情況下展現在一個方法接受接口作為參數,該接口的實作和傳遞對象給方法則交由你來做。
是以,接口的一種常見用法是政策設計模式。
編寫一個方法執行某些操作并接受一個指定的接口作為參數。可以說:“隻要對象遵循接口,就可以調用方法” ,這使得方法更加靈活,通用,并更具可複用性。
例如,類 Scanner 的構造器接受的是一個 Readable 接口。
你會發現 Readable 沒有用作 Java 标準庫中其他任何方法的參數——它是單獨為 Scanner 建立的,是以 Scanner 沒有将其參數限制為某個特定類。通過這種方式,Scanner 可以與更多的類型協作。如果你建立了一個新類并想讓 Scanner 作用于它,就讓它實作 Readable 接口。
假設你有一個類沒有實作 Readable 接口,怎樣才能讓 Scanner 作用于它呢?
可以再次使用擴充卡模式,通過關鍵字 interface 提供的多繼承。因為你可以以這種方式在已有類中增加新接口,是以這就意味着一個接受接口類型的方法提供了一種讓任何類都可以與該方法進行适配的方式。這就是使用接口而不是類的強大之處。
8 接口字段
接口的字段都自動 static final ,是以成為了建立一組常量的友善工具。
在 Java 5 之前,這是産生與 C 或 C++ 中的 enum (枚舉類型) 具有相同效果的唯一方式。是以你可能在 Java 5 之前的代碼中看到
自 Java 5 開始,我們有了更加強大和靈活的關鍵字 enum,那麼在接口中定義常量組就顯得沒什麼意義了。然而當你閱讀遺留的代碼時,在很多場合你還會碰到這種舊的習慣用法。
8.1 初始化接口中的字段
接口中定義的字段不能是“空 final",但是可以用非常量表達式初始化。
因為字段是 static 的,是以它們在類第一次被加載時初始化,這發生在任何字段首次被通路時。
這些字段不是接口的一部分,它們的值被存儲在接口的靜态存儲區域中。
9 接口嵌套
接口可以嵌套在類或其他接口中。
在類中嵌套接口就像非嵌套接口一樣,具有 public 或包通路權限的可見性。
作為一種新添加的方式,接口也可以是 private 的,例如 A.D(同樣的文法同時适用于嵌套接口和嵌套類)。
但 private 嵌套接口有什麼好處呢?
你可能猜測它隻是被用來實作一個 private 内部類,實作 private 接口是一種可以強制該接口中的方法定義不會添加任何類型資訊(即不可以向上轉型)的方式。
接口中的元素必須是 public 的——是以嵌套在另一個接口中的接口自動就是 public 的
不能指明為 private
-
當實作某個接口時,并不需要實作嵌套在其内部的接口。同時,private 接口不能在定義它的類之外被實作。
添加這些特性的最初原因看起來像是出于對嚴格的文法一緻性的考慮,但是我通常認為,一旦你了解了某種特性,就總能找到其用武之地。
10 接口和工廠方法模式
接口是多實作的途徑,而生成符合某個接口的對象的典型方式是工廠方法設計模式。
不同于直接調用構造器,隻需調用工廠對象中的建立方法就能生成對象的實作——理論上,通過這種方式可以将接口與實作的代碼完全分離,使得可以透明地将某個實作替換為另一個實作。
如果沒有工廠方法,代碼就必須在某處指定将要建立的 Service 的确切類型,進而調用恰當的構造器。
為什麼要添加額外的間接層呢?一個常見的原因是建立架構。
11 總結
認為接口是好的選擇,進而使用接口不用具體類,這具有誘惑性。幾乎任何時候,建立類都可以替代為建立一個接口和一個工廠。
很多人都掉進了這個陷阱,隻要有可能就建立接口和工廠。這種邏輯看起來像是可能會使用不同的實作,是以總是添加這種抽象性。這變成了一種過早的設計優化。
任何抽象性都應該是由真正的需求驅動的。當有必要時才應該使用接口進行重構,而不是到處添加額外的間接層,進而帶來額外的複雜性。這種複雜性非常顯著,如果你讓某人去處理這種複雜性,隻是因為你意識到“以防萬一”而添加新接口,而沒有其他具有說服力的原因——好吧,如果我碰上了這種設計,就會質疑此人所作的所有其他設計了。
恰當的原則
優先使用類而不是接口。從類開始,如果使用接口的必要性變得很明确,那麼就重構。
接口是一個偉大的工具,但它們容易被濫用。