structure
封裝,是要裝什麼? 在了解封裝之前,我們先來看看沒封裝的程式吧
val isElectric = false
val wheelsAmount = 4
val brand = "Toyota"
fun createCar() = Triple(isElectric,wheelsAmount,brand)
嗯嗯,不知所云,完全不知道我在幹嘛
那一樣的東西如果給他一層抽象的封裝呢?
val isElectric = false
val wheelsAmount = 4
class Car(
isElectric:Boolean,
wheelsAmount:Int,
brand:CarBrand
){
fun drive(){
//...
}
}
enum CarBrand {
Toyato, Tesla
}
fun createCar(brand:CarBrand):Car = Car(isElectric,wheelsAmount,brand)
fun Car.checkState(){
}
這不就清楚多了嗎?
另一方面,封裝不僅可以讓類別持有屬性,還能為該類別定義屬於他的方法,像是這邊的 drive
現在來比較一下兩者的差異,封裝後的程式
CarBrand
可以說是好處多多,超讚的推推
上面我們已經了解了將抽象概念封裝後的好處,那封裝之後,我們還該注意許多細節,尤其是封裝的資訊可見度,從結論來說,我們實作封裝時,應該盡可能公開越少細節,且會針對不同的設計而選擇適合的 visibility modifier
公開資訊,比隱藏資訊簡單的多,但通常也會是造成麻煩的來源,急得前面的文章說過了可變點限制的問題嗎?
class UserProfileViewModel(){
val profile = MutableLiveData<Profile>()
}
class UserProfileFragment(){
private val viewModel:UserProfileViewModel by viewModel()
}
同一個例子,那你看我們現在公開了 profile
出去,所有能存取他的其他代碼,都有可能利用 MutableLiveData
去更改資料
class UserProfileViewModel(){
private val _profile = MutableLiveData<Profile>()
val profile: LiveData<Profile>
get() = _profile
}
而我們通常的寫法是這樣,對外公開讀取,讓寫入限制在一個類別裡,那到底如何定義要公開哪些資訊呢?
除非必要,否則一律不公開,而對寫入操作的公開則應更加嚴謹
資訊隱藏可以說是類別設計中非常重要的一點,資訊隱藏的帶給我們的好處,遠遠超過資訊公開,可以將每個公開點都視為一種依賴,公開的資訊越多,外界對這個類別的耦合程度就越高,在修改公開區塊時,就需要考量到越多事情,我們應盡可能透過資訊隱藏限制範圍
透過 visibility modifier 做資訊隱藏的方式,都在這篇 會失控的變數範圍 了
而在前一個範例中,我們看到了 enum
,他是另一種封裝與資訊隱藏的好範例
enum CarBrand {
Toyato, Tesla
}
這是官方的使用方法,封裝一系列有限的類別,並用命名達到可讀性,那如果我們在這以上,再加點東西如何,比如後端給了我們用戶身份識別數字
enum class UserPermission(val value: Int) {
Normal(0), Admin(1) , SuperAdmin(2)
companion object {
fun fromInt(value: Int) = CoffeeType.values().firstOrNull { it.value == value }
}
}
我們可以利用建構子的方式定義,而在取值時,也是透過 .value
去拿到該數值
儘管enum
已經為我們提供了方便的封裝,但我們依據不同的情境有時需要更為複雜的類別來輔佐,這時 sealed class
就可以很大程度地幫助我們
比如為畫面行為設置 uiEvent
sealed class UiEvent {
data class Navigate(val route: String) : UiEvent()
object NavigateUp : UiEvent()
data class ShowSnackBar(val message: UiText) : UiEvent()
}
或是為 io 行為封裝狀態
sealed class Resource<out T>(
data: T? = null,
message: String? = null,
) {
class Success<out T>(data: T) : Resource<T>(data)
class Error<T>(
message: String,
data: T? = null,
) : Resource<T>(data, message)
//class Loading<T>(data: T? = null) : Resource<T>(data)
object Loading : Resource<Any>()
}
用 sealed class 可以做到更多複雜的狀態管理,彈性也會更大,能夠讓 class, object, data class 繼承
最後來聊聊,資訊公開與狄米特原則,被簡單描述為「對任何函式傳回的物件,不該再呼叫上頭之方法」
fun main() {
//wrong
val myCustomer = Customer()
val wallet = myCustomer.getWallet()
wallet.getCridetCard()//破壞封裝
wallet.subtractMoney()//錢包被別人動了
}
fun main() {
//better
val myCustomer = Customer()
myCustomer.payMoney()
}
封裝、抽象都是資訊隱藏的好方式,可以讓同一時間處理的本質複雜度減到最少,對資訊公開應盡可能嚴謹,並注意不要讓外部呼叫回傳物件的方法
structure
encapsulate? What is this for? before we discuss about it, let's check out code without it
val isElectric = false
val wheelsAmount = 4
val brand = "Toyota"
fun createCar() = Triple(isElectric,wheelsAmount,brand)
hmm, I have no idea what am I doing
What if we add encapsulate to the abstract idea?
val isElectric = false
val wheelsAmount = 4
class Car(
isElectric:Boolean,
wheelsAmount:Int,
brand:CarBrand
){
fun drive(){
//...
}
}
enum CarBrand {
Toyato, Tesla
}
fun createCar(brand:CarBrand):Car = Car(isElectric,wheelsAmount,brand)
fun Car.checkState(){
}
Isn't it clearer than before?
On The other side, encapsulate to class can holding state, and define function to class itself, like the drive
function
Now if we compare these two, the program after encapsulate
CarBrand
We discussed the advantage of encapsulate abstract idea above, and there are more detail we should care about, in conclusion, when we using encapsulate, we should hide information as much as we can, and choose between different visibility modifier
open visibility is earlier than hide information, but it usually is the source of problem, in previous article we talk about mutability
class UserProfileViewModel(){
val profile = MutableLiveData<Profile>()
}
class UserProfileFragment(){
private val viewModel:UserProfileViewModel by viewModel()
}
In the same example, we make the profile
visible, and all the class can access it, can use MutableLiveData
to change data
class UserProfileViewModel(){
private val _profile = MutableLiveData<Profile>()
val profile: LiveData<Profile>
get() = _profile
}
The common use case is this, we allow reading to open, and restrict writing inside the class. Usually we limited the visibility inside a class unless necessary
Hiding information is one of the most important in class design, the advantage if hiding information is more than open it, each visible point is kind of couple, we should limited it by hiding it
using visibility modifier
to hide data in this article 了
In the previous sample, we saw enum
, it is the other way to encapsulate and hide data
enum CarBrand {
Toyato, Tesla
}
this is official way, encapsulate a series of class, using naming for readability, but if we add a little more above this, like number to identify permission
enum class UserPermission(val value: Int) {
Normal(0), Admin(1) , SuperAdmin(2)
companion object {
fun fromInt(value: Int) = CoffeeType.values().firstOrNull { it.value == value }
}
}
We can define it by using constructor, and when we need the value, we can get it by .value
Although enum
provides convention encapsu.state, sometimes we need. complexity class to use, and sealed class
can help us
like setting uiEvent
for user behavior
sealed class UiEvent {
data class Navigate(val route: String) : UiEvent()
object NavigateUp : UiEvent()
data class ShowSnackBar(val message: UiText) : UiEvent()
}
or state for io operator
sealed class Resource<out T>(
data: T? = null,
message: String? = null,
) {
class Success<out T>(data: T) : Resource<T>(data)
class Error<T>(
message: String,
data: T? = null,
) : Resource<T>(data, message)
//class Loading<T>(data: T? = null) : Resource<T>(data)
object Loading : Resource<Any>()
}
with sealed class
can do more complexity state management, and more flexibility to allow class, object, data class
to inheritance
in the finally, the law of demeter is describe about shouldn;t call method on anything return by function
fun main() {
//wrong
val myCustomer = Customer()
val wallet = myCustomer.getWallet()
wallet.getCridetCard()//破壞封裝
wallet.subtractMoney()//錢包被別人動了
}
fun main() {
//better
val myCustomer = Customer()
myCustomer.payMoney()
}
encapsulate and abstract is good solution to hide information, can decrease the complexity in same time, we should always restrict data visibility, and mind on law of demeter