16
From J to K : 引入 Data Class 對整體架構造成的影響 – smallufo – Medium (medium.com)
IngramChen 積分 9 編輯於

這篇文只能舉小例子,不過

data class CoordinatePlace(
  val coordinate: Coordinate,
  val place: String)

我到這邊就會停了,不會再 refactor 下去。因為像是 place.coordinate.lng 多一層存取對我來說不會是個問題,(多到兩層以上我才會開始煩惱)。

kotlin 可以用 with , apply 等等工具減少很多不必要的 code,為了少一層開始抽 ICoordinate 不值得啊。(當然也可能是因為只能選小的範例)

-------

然後,有關繼承。OOP 寫久了之後,我的經驗是可以被設計有繼承的關係,通常是有類似的 行為,而不是有類似的資料。

也就是說,我不傾向因為它們有很多共同的欄位,所以就用繼承來解決程式碼重用。我最少得觀察到一個以上的行為、動作,才會開始思考繼承,而且通常會變成 interface (因為只有行為,沒有資料)

為什麼會有這種結論?因為共同欄位用繼承來重用程式經不起時間的考驗,只有設計初時可以用,等到加需求後就… 真的就是補丁硬改,改不動的就 workaround,那程式碼真是令人痛心。

-------

繼續以 Coordinate 為例, 假設我們有求兩個座標距離的需求:

data class Coordinate(... omit) {
   fun distanceTo(other:Coordinate): Double
}

然後有名稱的地點 CoordinatePlace 也要算距離,方法一是直接拿裡面的 coordinate 來求距離,方法二就是加上同樣的 method:

data class CoordinatePlace(val coordinate:Coordinate) {
   fun distanceTo(other:Coordinate): Double {
      return coordinate.distanceTo(other)
   }
}

方法二可以封裝 CoordinatePlacecoordinate 欄位 (欄位能少曝露就少曝露),到此,distanceTo 這個 method 就會有機會抽成一個 interface,因為它是 行為 ,然後有求距離需求的 class 實作它就行了。

這種不依賴 coordinate.lng 等等內部資料的高階行為才會有資格被抽出去,也因為 method signature 沒有牽扯到太多的資料欄位,所以未來擴充時有很大的彈性。

在設計 model 之間的互動時,應該要多依賴上面 distanceTo 這種高階的行為,而不是直接開內部欄位 .lng , .lat 給別的 model 算,沒有高階行為的 model,才是真正的 Anemic Domain Model。

ps. data class 的欄位都是 public 的,所以這個例子有點封裝失敗。不過上面的重點是行為。

IngramChen 積分 2

另一個有名的例子是 java8 的 java.time 的設計。

LocalDate 和 LocalDateTime 並沒有繼承關係,明明 LocalDateTime 也有 year, month, day 等等欄位,意思也很像

natsu 積分 0 編輯於

ps. data class 的欄位都是 public 的,所以這個例子有點封裝失敗。不過上面的重點是行為。

資料不能 reuse 倒也罷了,因為一個物件頂多十幾個欄位

可是行為若不能 reuse 就比較麻煩 ...

既然如此,就把整個與核心properties 無關的推導之值全部 interface 化吧,最後,單獨一個 Coordinate 的物件模型變成如此

不過要 Java 8+ 才能在 interface 上實作 method ...

還在用之前版本的可能還是需要用到繼承 ...