everything the light touch is our kingdom
Lion king
english version is down below
變數的範圍決定了誰可以存取他,為什麼這需要在意呢?為什麼我們不要把每個變數都變成全域的讓大家都可以存取呢?
事實是那會非常危險,考量到潛在的可變性,無上限的記憶體用量,race condition等,把全部變數都變成全域可不是個好主意
那我們有規則可循嗎?要如何寫出更好的設計呢?我可可以遵循幾個規則
第一,讓變數的範圍盡可能的小,如果一個變數只在迴圈裡使用,就只在迴圈裡聲明他
從這
val userState
if(isAdmin) {
userState = UserState.Valid
} else {
userState = UserState.InValid
}
val notes:List<Note> = ...
for(i in notes.indice){
notes[i]
}
locations.forEach{
it.lat
it.lng
}
變成這樣
val userState = if(isAdmin) UserState.Valid else UserState.InValid
val notes:List<Note> = ...
for (note in notes){
}
locations.forEach{ (lat,lng) ->
}
第二,了解你的語言語法
不同的語言語法會有些微差異,拿 Kotlin 為例,要了解val/ var 的getter/ setter,val 並不總是不變的
第三,全域變數不應該是可變的,出非你有好的理由如此設計,因為全局可變變數,會有從不同組件無法預期的變化
那單例模式呢?他算是違反了規則嗎?
在安卓裡面,有幾個常見使用單例情境,像是與網路連線,讀取靜態資料等等,單例的設計核心在於某個東西,因有唯一的一個,且可讓全局讀取,如果在單例之中,有著可變的變數,那仍然違反的好的設計原則
變數本身應盡可能地靠近使用它的地方,而使用變數的範圍則應盡可能的小
有別於區域變數和全局變數,在Kotlin 裡另外還有其他的範圍,如果你熟悉語法,你應該已經知道了
private
protected
internal
在物件導向裡有 Visibility modifiers , 在函式裡的變數是區域變數,在頂層聲明的變數的全域變數,現在來了解例一個成員變數 member variable
open class Adapter(){
var count = 0
private set
protected var clickEvent: (Int) -> Unit = {}
private var regexRule = ""
}
class NestAdapter:Adapter(){
init {
//access getter to count
count
//access getter and setter to clickEvent
clickEvent = {
println(it)
}
// ERROR to access regexRule
regexRule
}
}
count
是 Adapter
類別的成員變數, 預設的可見設置是public, 但我把他的 setter 設置成 private, 從結論來看,他的 getter 是 public, 但 setter 是 private, 所以他只能從類別內部修改,但外部可以讀取
clickEvent 可以從聲明他的類別中存取,還有其子類
除了成員變數 visibility modifier 還能在檔案類型用
//DataStoreFactory.kt
private val Context.dataStore by preferencesDataStore(
name = USER_PREFERENCES_NAME
)
他改變了 extension variable 的 visibility 限制於該檔案內
最後要討論的 visibility modifier 是 internal, 他是要控制模組化之間的可見度
每個模組是為一個功能所建造,而每個模組都能有其子模組,要讓範例簡單一點,我們會在網路模組寫出下面代碼
//:core:network Constant.kt
internal const val BASE_URL = BuildType.BASE_URL
現在 BASE_URL
對 :core:network
模組是全域的, 但對引入 :core:network
模組的其他模組是不可見的, 所以 :data:books
和 :data:reviews
的模組不會看到 BASE_URL
範圍和可見性特別重要,他們應該越受限制越好,範圍限制越多,工程師就能越專注在眼前的業務邏輯和程式,我們人類有著受限的智能管理,如果變數能越廣泛的存取寫入,就越可能遇到不可預期且難以判斷的錯誤
第一個是聲明了變數,但沒有使用,他就佔用了記憶體直到系統將其回收掉
另一個是,生命週期短的持有了生命週期長的引用
如果你的 fragment 持有 application 的引用,那 fragment 就無法被被系統回收掉,因為他會認為 fragment 對 application 還有未完成的任務,所以才會保有引用
class Fragment {
private val app = this.requireActivity.application
}
這樣的問題並不是只有安卓才有,你應該熟悉你使用的平台上的生命週期,並注意錯誤的變數使用
The scope of a variable decides how to access a variable, why should we care, why don't we make everything global so everyone can access them?
The truth is that is very dangerous, consider the mutability from multiple place, unlimit memory usage, race condition ...etc, make everything as global is not a great idea
So what is the rule? What should we do for better design?
There are following rule
First, make scope as small as possible
if one variable only used in for loop, then declare it inside for loop
from this
val userState
if(isAdmin) {
userState = UserState.Valid
} else {
userState = UserState.InValid
}
val notes:List<Note> = ...
for(i in notes.indice){
notes[i]
}
locations.forEach{
it.lat
it.lng
}
to this
val userState = if(isAdmin) UserState.Valid else UserState.InValid
val notes:List<Note> = ...
for (note in notes){
}
locations.forEach{ (lat,lng) ->
}
Second, know about your language syntax
Different language work differently, take Kotlin as example, the different between var and val is getter and setter, val is not always immutable
Third, your global shouldn't be mutable, unless you have good reason, because it might be changed by other module unexpectly
What about singleton pattern, does it break the rule, in Android, there are common usage of singelotion, for instance, used for network connection, and access to read const data, the core to singleton is there should be onw and the only one instance
variable should be declare as closer as possible to where it used, and the range should be as small as possible
Apart from local variable and global variable, there are other scope in Kotlin, if you are familiar with Kotlin syntax, you should already know about
private
protected
internal
there are Visibility modifiers in oop, a variable inside function is a local variable, and a variable declare in top level is global variable, now let me introduce a new variable, member variable
open class Adapter(){
var count = 0
private set
protected var clickEvent: (Int) -> Unit = {}
private var regexRule = ""
}
class NestAdapter:Adapter(){
init {
//access getter to count
count
//access getter and setter to clickEvent
clickEvent = {
println(it)
}
// ERROR to access regexRule
regexRule
}
}
the count
is a member variable of Adapter
class, the default visibility is public, but I set its setter to private, in collusion, its getter is public, but setter is private, so it can only change the value inside the class
clickEvent could be access from the class declare it, and all its subclass
Beside the member variable, visibility modifier could be used for file
//DataStoreFactory.kt
private val Context.dataStore by preferencesDataStore(
name = USER_PREFERENCES_NAME
)
which change the visibility of extension variable to only inside this file
the last visibility modifier we gonna discuss is internal, this modifier is used for control visibility in modularization
each is build for one feature, and it could also have submodule, to make demo simple, I will declare a variable like this
//:core:network Constant.kt
internal const val BASE_URL = BuildType.BASE_URL
now the BASE_URL
is global inside :core:network
module, but private to other module import :core:network
module, so :data:books
and :data:reviews
can't access to BASE_URL
Scope and visibility is extra important, it should be as restrict as possible, the smaller the scope is, the less thing engineer need to concern, we human have limited intellectual manageability, if there is a variable is widely access, and can be written by everywhere, it is possible cause unexpected issue and hard to be caught.
There are two common mistake kane cause memory leak in Android, the first one is declare a variable but you never use it, so will take space in memory until garbage collect recycle it, the other is more difficult to find out
a class has shorter lifecycle holding a reference to a longer lifecycle
if your fragment holding a reference to application, the fragment can't be recycled due to to garbage collect algorithm assume the fragment class have something to do with application
class Fragment {
private val app = this.requireActivity.application
}
A similar issue is not Android only, you should be familiar with the lifetime of all your using platforms, and be aware of wrong usage of variables.