參考來源 : https://arrow-kt.io/learn/immutable-data/intro/
前兩天提到的 Data Class, Sealed Class, Enum 與 Value Class 配合不可變的變數。是在 Kotlin 中作 Domain Modeling 的良好方法。複習一下前幾天的文章
所以如果我們想要精確地建模一個領域,我們經常會得到大量的巢狀類,每一個都代表一種特定的物件模型
data class Person(val name: String, val age: Int, val address: Address)
data class Address(val street: Street, val city: City)
data class Street(val name: String, val number: Int?)
data class City(val name: String, val country: String)
雖然 Kotlin 有提供 data class 的 copy, 但只限第一層。如果我們只想轉變(Transform - aka 修改)一個第三層的值,我們還是需要進行多次的複製。
fun Person.capitalizeCountry(): Person =
this.copy(
address = address.copy(
city = address.city.copy(
country = address.city.country.capitalize()
)
)
)
Arrow KT 為這個問題提供了一個解法,即 optics。 optiocs 是代表在更大的型別對內部的一個(或多個)值的訪問的值。可以用光學的透鏡來類比。所以取 Optics 這個名字
例如,我們可能有一個焦點在 Person 的 address field 上。通過組合不同的光學,我們可以關注巢狀的元素,例如 Person 中的 address 中的 city 。程式碼會比描述更有說服力,所以讓我們看看使用 optics 後上面的例子如何改進。
使用 Arrow Optics 最簡單的方法是通過其編譯器 plugins 。在把它到您的 gradle,您只需要標記每個希望生成光學的類,使用 @optics 注解。
import arrow.optics.*
@optics data class Person(val name: String, val age: Int, val address: Address) {
companion object
}
@optics data class Address(val street: Street, val city: City) {
companion object
}
@optics data class Street(val name: String, val number: Int?) {
companion object
}
@optics data class City(val name: String, val country: String) {
companion object
}
煩人的 compaion object : 要在每個類中都有一個,即使它是空的。這是由於 KSP 的限制
因為 optics 會自動生成程式,所以要先作一次 compiler 才能用到 optics 的 method
Optics 為每個 field 生成 optics ,可以在 class 下使用。例如,Person.address 是聚焦在 address 上的Optics。此外,可以使用 "." 一路到你想關注的欄位。在這種情況下,Person.address.city.country 代表正確聚焦在我們想要轉換的欄位上。使用它,我們可以用兩種方式重新實現 capitalizeCountry:
fun Person.capitalizeCountryModify(): Person =
Person.address.city.country.modify(this) { it.capitalize() }
fun Person.capitalizeCountryCopy(): Person =
this.copy {
Person.address.city.country transform { it.capitalize() }
}
My Bag 雖然前幾天推過了,但這個版本的舞台很棒