2024-04-26|閱讀時間 ‧ 約 26 分鐘

Java 8 初探 - Default Methods

此為過去的舊文,2014 年 5 月 3 日初次發表於 logdown。

今天離開 Lambda 相關的議題,看一下 Java 8 另一個和語言相關的改變:default methods。過去,Java 的介面只能宣告函式卻不能提供實作 (或是說是定義函式,定義函式和宣告函式的差別就是有沒有實作),因此大多數的 library 設計都將工具函式 (static utility methods) 放在類別中,這基本上沒什麼大問題,但是對有些潔癖的人來說,就會考慮要不要把該類別的建構子設為 private?

另外,Java 不支援多重繼承,但能實作多個介面,所以在Java的函式庫裡常會看到一種設計慣例:介面宣告需要的函式,透過抽象類別提供共用的預設實作,最後由預設類別提供完整的實作,例如:TableModel 宣告 JTable 所需要的最小函式集合,AbstractTableModel 提供部分共用的實作 (listeners的管理),最後 DefaultTableModel 提供一個完整的實作。這樣的慣例引起另一個問題:但是當 A 類別繼承了 B 類別,但又想使用 C 介面的抽象類別實作就沒辦法了 (因為不能多重繼承),A 類別只能自己實作 C 介面,某種程度上算是一種 duplicated code。

自己在設計 Comic Surfer 的過程中,為了讓既有的外掛和主程式能維持 binary compatibility,也採用上述的慣例,在公開的外掛 SDK 裡,同時放了介面和抽象類別,不過在開發文件上鼓勵開發者以繼承抽象類別的方式開發外掛,這樣的設計是當我想在介面中新增函式的宣告,同時在抽象類別中為新增的函式提供預設的實作,如此,即使外掛沒有用新的 SDK 進行修改,還是能在新版的 Comic Surfer 中掛載使用,而不會讓 JVM 抱怨說某個類別沒有提供完整的實作 (不相容)。

上述的問題,在這次的 default methods 得到了一個不錯的解法。首先,工具函式不需要再放在類別中了,如下所示,靜態函式的實作能直接放在介面中。


接著,抽象類別某種程度上可以被取代 (如果抽象類別帶有成員變數就沒辦法),假設要替溫度感測器設計一個介面,可以像 TemperatureSensor 介面,並提供二個 default methods:getTemperatureInCelsius()getTemperatureInFahrenheit()分別將絕對溫度轉成常用的攝氏溫度和華氏溫度 (這個例子是一個簡化的示意。不算是一個好設計,因為這轉換應該算是能抽離到外部變成一個獨立類別)。這時過去需要提供類似 AbstractTemperatureSensor 的抽象類別就完全不需要了,因為實作已經放在介面裡了。


不需要抽象類別,提供了一個設計上的彈性,例如要替一個溫度感測裝置控制器 (AbstractDeviceControl) 寫 Adapter時,在沒有 default methods 前,因為單一繼承,要思考是讓 Adapter 繼承 AbstractDeviceControl 比較好,還是繼承 AbstractTemperatureSensor 比較好?當然,用 composition 解決更好,只是有時候會多寫些程式。現在可以像 TemperatureSensorDevice 用透過實作介面的方式取得預設實作,然後將唯一的繼承機會讓給其他可能需要的設計。


然後,如果要替介面新增函式,也可以透過直接在介面中提供預設實作,提供向下相容性。例如新增蘭金溫度表示法 (Rankine scale) 到 TemperatureSensor 中,並提供預設的實作,這時 TemperatureSensorDevice 不需做任何修改就能夠使用。


既然是介面,當然能用擴充 (extend) 的方式產生新介面,例如,某些國家習慣用華氏溫度,所以很多感測器都只提供華氏溫度,這時可以擴充 TemperatureSensor 介面產生新介面 FahrenheitTemperatureSensor,在擴充的時候,可以用 abstract 關鍵字將一個有預設實作的 getTemperatureInFahrenheit() 重新宣告為抽象函式,或是複寫既有的實作,例如複寫 getTemperatureInCelsius() 變成直接取華氏溫度轉攝氏溫度,不用多轉一次絕對溫度然後再轉攝氏溫度。


其實,default methods 似乎也引起不小的討論,因為 default methods 加上可以實作多個介面,已經有點像 C++ 的多重繼承了,只差在沒辦法繼承成員變數而已,是好是壞就看怎麼使用了。我個人覺得還蠻方便的,如果不是因為 default methods 要到 Java 8 才有,我倒是考慮將 Comic Surfer 的外掛介面和工具函式類別用 default methods 改寫。

分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.