People don't change.
Times do.
John Wick
程式初期,筆者覺得 var
超好用,可讀可寫一直爽,到後期一點變成 val
超棒的,説一就一不會變,直到寫了更多程式後,我終於找到內心的平衡了
此表格整理了 Effective Kotlin 的一些重點
不可變 | 可變 | |
---|---|---|
多線程操作 | 安全,因為只讀 | 不安全,因為有機會 race condition |
易於推論 | 可,總是固定的 | 不可,有潛在變化的可能性 |
快取 | 可,因為他不會被改變 | 麻煩,要在每個變更點以線程安全方式更新快取 |
預防性拷貝 | 免,因為無法被改變 | 要,而且要考慮到深淺拷貝 |
建構其他物件 | 可,最適合 | 可,但可能為該物件增加了更多的更動點 |
hashcode/ map | 符合設計 | 有潛在違反設計可能 |
從你應知道的 Kotlin開始,就提過了 val/ var 的差異在於 getter/ setter,總結一下就是 getter/ setter 允許變化,但 getter 的變化要先定義,且較不易察覺
如果我們有一段
const val HELLO = "hello"
fun checkUserName(name:String){
if(name == HELLO){
//do something
}
}
編譯後會變成這樣
const val HELLO = "hello"
fun checkUserName(name:String){
if(name == "hello"){
//do something
}
}
算是完全沒有變化點的方式了
假設我們定義了驗證的介面,並想要為密碼做正規檢查
interface Validator {
val regexRule:String
fun isValidate():Boolean
}
class PasswordValidator:Validator {
override val regexRule = "..."
}
但是呢,語法上可以變成var
而不報錯
class PasswordValidator:Validator {
override var regexRule = "..."
}
如果這種細節沒被檢查到,那這個密碼驗證的正規檢查便不再可信了,下面兩種作法會好得多,不應被變動的東西就不要給他機會
class PasswordValidator:Validator {
companion object {
const val regexRule = "..."
}
}
class PasswordValidator:Validator {
...
Constant.PASSWORD_REGEX_RULE
}
object Constant {
const val PASSWORD_REGEX_RULE = "..."
}
跨層級的變動是最危險的,這個層級是指什麼呢?
module, layer, class, object ...etc, 我們應盡可能隱藏資訊,盡可能減少曝露的細節,拿 viewModel 來說吧
class UserProfileViewModel(){
var profile = MutableLiveData<Profile>()
}
class UserProfileFragment(){
private val viewModel:UserProfileViewModel by viewModel()
}
data class Profile(
val id:Int,
val name:String,
var photo:String?= null
)
試問,我在 viewModel 一行代碼裡面曝露了幾個變化點?
.
.
.
.
.
.
.
.
.
3個,厲害吧XD
而這三個都可以在能存取到的其他類別做修改,如果今天 viewModel 是 share 的,根本無法預期資料會如何變化
class UserProfileViewModel(){
private val _profile = MutableLiveData<Profile>()
val profile:LiveData<Profile>
get() = _profile
fun updateProfile(profile:Profile){
// api request to backend for update
// if success
// update _profile
}
}
data class Profile(
val id:Int,
val name:String,
val photo:String?= null
)
一起來拆開看看 data class copy 實際上做了什麼吧~~
data class User(
val name:String,
val age:String
)
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
沒錯,他給了我們一個新的實例,但如果今天裡面是長這樣
data class User(
val name:String,
val age:String,
val favoriteMovie:MutableList<String> = emptyList()
)
copy 時 favoriteMovie 會是同一個 instance 喔
我們應該為上述情境使用深拷貝嗎?
fun deepCopy():MyCustomClass {
val JSON = Gson().toJson(this)
return Gson().fromJson(JSON, MyCustomClass::class.java)
}
其實,不建議,除非該做法完美符合業務邏輯
data class 本身設計更適合不可變資料,如果使用者需要資料變動,再利用copy 的方式改變即可
欲知更多資訊??? 快來報名 10/20 號的 Effective Kotlin 讀書會
I collect some point from Effective Kotlin
Immutable | Mutable | |
---|---|---|
Parallel operator | safe, read only | Notsafe, race condition |
figure out error | easy, always same | not easy, might change at run time |
cache | yes, always same | annoying, need update in every change point, also race condition |
defensive copy | no need | yes, think about shadow copy and deep copy |
use for build object | yes, appropriate | yes, but increase potential multable point |
hashcode/ map | respeact contract | might break the contract |
From everything you should know about Kotlin, I mentioned about different between val/ var, in conclusion both getter/ setter allow change, but getter require defined first, and it is harder to find out
For sample code like this
const val HELLO = "hello"
fun checkUserName(name:String){
if(name == HELLO){
//do something
}
}
It will compile to this
const val HELLO = "hello"
fun checkUserName(name:String){
if(name == "hello"){
//do something
}
}
It is a safe way to maintain mutability
For example we define an interface for validate, and a class for check password regex rule
interface Validator {
val regexRule:String
fun isValidate():Boolean
}
class PasswordValidator:Validator {
override val regexRule = "..."
}
But in Kotlin syntax it can change to var
without syntax error
class PasswordValidator:Validator {
override var regexRule = "..."
}
detail like this is dangerous, the regex for password check is no longer be trusted, the following solution is better. we should never give chance t immutable things
class PasswordValidator:Validator {
companion object {
const val regexRule = "..."
}
}
class PasswordValidator:Validator {
...
Constant.PASSWORD_REGEX_RULE
}
object Constant {
const val PASSWORD_REGEX_RULE = "..."
}
Mutable across layer is danger, what does th layer means?
module, layer, class, object ...etc, we should hide our information as much as possible, take viewmodel as example
class UserProfileViewModel(){
var profile = MutableLiveData<Profile>()
}
class UserProfileFragment(){
private val viewModel:UserProfileViewModel by viewModel()
}
data class Profile(
val id:Int,
val name:String,
var photo:String?= null
)
How many mutable points I exposed in one line of code of viewmode?
.
.
.
.
.
.
.
.
.
Three, amazing LOL
var, mutableLiveData, Profile.photo
those three mutable point could be change from everywhere can access them, we can't expect when and how the data change
class UserProfileViewModel(){
private val _profile = MutableLiveData<Profile>()
val profile:LiveData<Profile>
get() = _profile
fun updateProfile(profile:Profile){
// api request to backend for update
// if success
// update _profile
}
}
data class Profile(
val id:Int,
val name:String,
val photo:String?= null
)
Let's check it out how does copy works in data class
data class User(
val name:String,
val age:String
)
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
That's right, we received a new instance, but if we contain a mutablelist
data class User(
val name:String,
val age:String,
val favoriteMovie:MutableList<String> = emptyList()
)
the MutableList
will be same instance for original data class and copied data class
Should we use deep copy for the above sample?
fun deepCopy():MyCustomClass {
val JSON = Gson().toJson(this)
return Gson().fromJson(JSON, MyCustomClass::class.java)
}
Unless the situation fit your business logic, otherwise won't recommend
the data class itself suit for immutable data, if user need data change, the design force user use copy to protect original data
For more information??? Sign up for Effective Kotlin study jam
Reference:
Effective Kotlin