[Day 11] Class (類別) vol. 2
制定合理的屬性可見性
延續昨天的話題, 其實如果是一個 balance 的屬性, 我們不應該直接可以讓人修改,應該要特別制定函數提供別人呼叫修改
所以在這把原本的 set 都加上了 private 的可見性
var balance = _balance
get() {
println("取得 balance: $field")
return field
}
private set(value) {
println("更新 balance: $value")
field = value
}
var balaceUpdateTime: LocalDateTime? = null
private set(value) {
println("更新 balance 時間: $value")
field = value
}
如此在 main 就無法直接設定這兩個變數了
接著我開放了兩個函數, 提供儲值和扣錢的功能, 並且會紀錄更新的時間
fun increaseBalance(amount: BigDecimal) {
println("儲值...$amount")
if (amount.compareTo(BigDecimal.ZERO) < 0) throw RuntimeException("輸入金額必須大於或等於 0")
balance = balance.add(amount)
balaceUpdateTime = LocalDateTime.now()
}
fun decreaseBalance(amount: BigDecimal) {
println("消費金額...$amount")
if (amount.compareTo(BigDecimal.ZERO) < 0) throw RuntimeException("金額必須大於或等於 0")
if (amount.compareTo(balance) > 0) throw RuntimeException("輸入金額不能大於結餘(balance)")
balance = balance.subtract(amount)
balaceUpdateTime = LocalDateTime.now()
}
在 main 呼叫後
wallet.increaseBalance(BigDecimal(100))
println("結餘(balance): ${wallet.balance}, 修改時間: ${wallet.balaceUpdateTime}")
wallet.decreaseBalance(BigDecimal(50))
println("結餘(balance): ${wallet.balance}, 修改時間: ${wallet.balaceUpdateTime}")
可以看到以下 output 的結果
取得 balance: 200
儲值...100
取得 balance: 200
更新 balance: 300
更新 balance 時間: 2020-09-30T09:27:27.852
取得 balance: 300
結餘(balance): 300, 修改時間: 2020-09-30T09:27:27.852
消費金額...50
取得 balance: 300
取得 balance: 300
更新 balance: 250
更新 balance 時間: 2020-09-30T09:27:27.853
取得 balance: 250
結餘(balance): 250, 修改時間: 2020-09-30T09:27:27.853
會發現只要顯示 balance, 的確就會呼叫 balance 的 get() 印出 取得 balance: xxx
設定 balance 就會呼叫 balance 的 set() 印出 更新 balance: xxx
計算屬性
昨天講到 backing field,但其實有個狀況是不需要產生 backing field。
class Dice() {
val rolledValue
get() = (1..6).shuffled().first()
}
因為每次讀取時,都會重新計算,沒有初始值或預設值,所以就不需要 backing field 來存放。
val dice = Dice()
println("骰子擲出點數: ${dice.rolledValue}")
println("骰子擲出點數: ${dice.rolledValue}")
結果:
骰子擲出點數: 3
骰子擲出點數: 2
Race condition
還有一個很特殊的狀況,如果今天多新增了一個 PaymentWay 的 class 在 Wallet 裡面使用, showPaymentWay(),如果僅僅是做 null 判斷,compiler 會認為這樣在 multi thread 時會有危險
class PaymentWay(val name: String)
class Wallet(val id: Long, _balance: BigDecimal, _createTime: LocalDateTime) {
var paymentWay: PaymentWay? = PaymentWay("Apple Pay")
fun showPaymentWay() {
if (paymentWay != null) {
println(paymentWay.name)
}
}
}
如下, 提示提到了可能會 PaymentWay 是 可變的屬性
可能會在某時間點被改變
在 Kotlin 我們可以輕鬆簡單的使用 also, let 匿名函數(這兩個方法之後會再提到)的特性來創造一個 local 的範圍,來確保不會有 race condition
fun showPaymentWay() {
paymentWay?.also { println(it.name) }
}
或
fun showPaymentWay() {
paymentWay?.let { println(it.name) }
}
最後再來複習一下,可見性
Kotlin 中的 class 可見性修飾符號(Visibility Modifiers)有以下幾種
- public: 其實可以不用寫,constructor, function, property 預設就是 public,代表是隨處都可以看見
- private: 在同一 class 下可以看見
- protected: 在同一 class 或 class 的子 class 下可以看見
- internal: 在相同 module 下可以看見
其實跟 function 那時提到的一樣
private constructor
constructor 預設都會是 public
但有時候會需要 private,像是當你不想要提供別人透過 contructor 來建立一個新物件的時候
就可以像這樣做,constuctor 是 private 的,無法訪問,然後透過 companion object (伴生物件)提供建立的方法,companion object 在後面會再提到。
class Foo private constructor(val someData: String) {
companion object {
fun constructorA(): Foo {
// do stuff
return Foo("someData")
}
}
}
今天就講到這裡啦!謝謝大家,我們明天見!
今日練習的程式在這: 請點我