top

Java 中實作代數資料型態的方式

回應 Jeff Chen 網友的留言:
http://www.codedata.com.tw/java/ ... gebraic-data-types/

如果沒理解錯,Jeff Chen 網友應該是想模擬 Haskell 中的 data constructor:
http://learnyouahaskell-zh-tw.cs ... gebraic_Data_Types_入門

也就是該文中的「值構造子」,以該文中的 Shape 為例:
  1. data Shape = Circle Float Float Float | Rectangle Float Float Float Float
複製程式碼
Circle、Rectangle 都是 Shape 型態,你可以用 Circle 10 20 10 這類方式構造一個 Shape,也可以用 Rectangle 這類方式構造一個 Shape,除此之外,沒有其他構造一個 Shape,Circle、Rectangle 都是 Shape 的值,而不是子型態。

Java 中不支援代數資料型態,你定義了一個 Shape 類別,建構式名稱就必須是 Shape,沒辦法有名稱為 Circle、Rectangle 的建構式。

不過物件導向中,如果有繼承關係,那麼子類會是一種(is a)父類,如果 Circle、Rectangle 繼承自 Shape,那麼使用 Circle、Rectangle 建構的值(或稱為物件),就會是一種 Shape,這樣的描述有點類似前面談 Haskell 時的描述…Circle、Rectangle 都是 Shape 的值,而不是子型態。

也因此,物件導向程式語言如果要模擬或實現代數資料型態,就是假借了「是一種」(is-a)的關係,來實現類似值建構式的概念,也就因此,大部份代數資料型態的模擬方式,都是利用了繼承來實現。

對於有直接支援函數式程式設計的 Scala 來說,也是如此,其主要是利用 case class 與 sealed class 來實現…
http://openhome.cc/Gossip/Scala/CaseClass.html
http://openhome.cc/Gossip/Scala/SealedClass.html

case class 主要是可以進行 Pattern match,而 sealed class 主要是實現 data constructor 的概念,從文中可以看出,他也利用了繼承。

不過在 Java 中,使用繼承來實現代數資料型態,會有個問題,例如你定義 Circle、Rectangle 繼承 Shape,雖然你不希望有其他子類別,不過沒有別的方式可以阻止其他人再繼承 Shape,建立一個 Triangle 之類的。

觀察 Scala 的 case class,你其實會明瞭,其本身使用了 apply 方法來建構實例,也就是運用了工廠(Factory )模式,也因此在 Java 開發者的函數式程式設計(2)代數資料型態 中,我就是運用了工廠方法,利用一個 List 介面,並以 nil()、list() 方法來構造值,也就是 List 的實作物件,如果 nil()、list() 等方法是 Lists 的靜態方法,使用起來的風格就會像是:
  1. Lists.nil(); // 就像 Haskell 的 []
  2. Lists.list(1, 2, 3, 4); // 就像 Haskell 的 [1, 2, 3, 4]
複製程式碼
這是另一種可行方式,當然,實作介面本身就是一種繼承,不過在大家遵守規範下,透過 nil()、list() 等方法來構造 List 的值,會比直接定義子類的方式,更接近 data constructor 的概念。

這是目前我所知道的方式,應該還有其他方式,畢竟語言不是一對一對應的,有經驗的網友們可以再討論…