kotlin修煉指南6-Sealed到底密封了啥

點(diǎn)擊上方藍(lán)字關(guān)注我,知識(shí)會(huì)給你力量

在代碼中,我們經(jīng)常需要限定一些有限集合的狀態(tài)值,例如:
網(wǎng)絡(luò)請(qǐng)求:成功——失敗 賬戶狀態(tài):VIP——窮逼VIP——普通 工具欄:展開——半折疊——收縮
等等。
通常情況下,我們會(huì)使用enum class來做封裝,將可見的狀態(tài)值通過枚舉來使用。
enum class NetworkState(val value: Int) {
SUCCESS(0),
ERROR(1)
}
但枚舉的缺點(diǎn)也很明顯,首先,枚舉比普通代碼更占內(nèi)存,同時(shí),每個(gè)枚舉只能定義一個(gè)實(shí)例,不能拓展更多信息。
除此之外,還有種方式,通過抽象類來對(duì)狀態(tài)進(jìn)行封裝,但這種方式的缺點(diǎn)也很明顯,它打破了枚舉的限制性,所以,Kotlin給出了新的解決方案——Sealed Class(密封類)。
創(chuàng)建狀態(tài)集
下面我們以網(wǎng)絡(luò)請(qǐng)求的例子來看下具體如何使用Sealed Class來進(jìn)行狀態(tài)的封裝。
和抽象類類似,Sealed Class可用于表示層級(jí)關(guān)系。它的子類可以是任意的類:data class、普通Kotlin對(duì)象、普通的類,甚至也可以是另一個(gè)密封類,所以,我們定義一個(gè)Result Sealed Class:
sealed class Result<out T : Any> {
data class Success<out T : Any>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
當(dāng)然,也不一定非要寫在頂層類中:
sealed class Result<out T : Any>
data class Success<out T : Any>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
這樣也是可以的,它們的區(qū)別在于引用的時(shí)候,是否包含頂層類來引用而已。
大部分場(chǎng)景下,還是建議第一種方式,可以比較清晰的展示調(diào)用的層級(jí)關(guān)系。
在這個(gè)例子中,我們定義了兩個(gè)場(chǎng)景,分別是Success和Error,它表示我們假設(shè)的網(wǎng)絡(luò)狀態(tài)就這兩種,分別在每種狀態(tài)下,例如Success,都可以傳入自定義的數(shù)據(jù)類型,因?yàn)樗旧砭褪且粋€(gè)class,所以借助這一點(diǎn),就可以自定義狀態(tài)攜帶的場(chǎng)景值。在上面這個(gè)例子中,我們定義在Success中,傳遞data,而在Error時(shí),傳遞Exception信息。
所以,使用Sealed Class的第一步,就是對(duì)場(chǎng)景進(jìn)行封裝,梳理具體的場(chǎng)景枚舉,并定義需要傳遞的數(shù)據(jù)類型。
?如果場(chǎng)景值不需要傳遞數(shù)據(jù),那么可以簡(jiǎn)單的使用:object xxxx,定義一個(gè)變量即可。
?
使用
接下來,我們來看下如何使用Sealed Class。
fun main() {
// 模擬封裝枚舉的產(chǎn)生
val result = if (true) {
Result.Success("Success")
} else {
Result.Error(Exception("error"))
}
when (result) {
is Result.Success -> print(result.data)
is Result.Error -> print(result.exception)
}
}
大部分場(chǎng)景下,Sealed Class都會(huì)配合when一起使用,同時(shí),如果when的參數(shù)是Sealed Class,在IDE中可以快速補(bǔ)全所有分支,而且不會(huì)需要你單獨(dú)補(bǔ)充else 分支,因?yàn)镾ealed Class已經(jīng)是完備的了。
所以when和Sealed Class真是天作之合。
進(jìn)一步簡(jiǎn)化
其實(shí)我們還可以進(jìn)一步簡(jiǎn)化代碼的調(diào)用,因?yàn)槲覀兠看问褂肧ealed Class的時(shí)候,都需要when一下,有些時(shí)候,也會(huì)產(chǎn)生一些代碼冗余,所以,借助拓展函數(shù),我們進(jìn)一步對(duì)代碼進(jìn)行簡(jiǎn)化。
inline fun Result<Any>.doSuccess(success: (Any) -> Unit) {
if (this is Result.Success) {
success(data)
}
}
inline fun Result<Any>.doError(error: (Exception?) -> Unit) {
if (this is Result.Error) {
error(exception)
}
}
這里我對(duì)Result進(jìn)行了拓展,增加了doSuccess和doError兩個(gè)拓展,同時(shí)接收兩個(gè)高階函數(shù)來接收處理行為,這樣我們?cè)谡{(diào)用的時(shí)候就更加簡(jiǎn)單了。
result.doSuccess { }
result.doError { }
所以when和Sealed Class和拓展函數(shù),真是天作之合。
那么你一定好奇了,Sealed Class又是怎么實(shí)現(xiàn)的,其實(shí)反編譯一下就一目了然了,實(shí)際上Sealed Class也是通過抽象類來實(shí)現(xiàn)的,編譯器生成了一個(gè)只能編譯器調(diào)用的構(gòu)造函數(shù),從而避免其它類進(jìn)行修改,實(shí)現(xiàn)了Sealed Class的有限性。
封裝?
Sealed Class與抽象類類似,可以對(duì)邏輯進(jìn)行拓展,我們來看下面這個(gè)例子。
sealed class TTS {
abstract fun speak()
class BaiduTTS(val value: String) : TTS() {
override fun speak() = print(value)
}
class TencentTTS(val value: String) : TTS() {
override fun speak() = print(value)
}
}
這時(shí)候如果要進(jìn)行拓展,就很方便了,代碼如下所示。
class XunFeiTTS(val value: String) : TTS() {
override fun speak() = print(value)
}
所以,Sealed Class可以說是在抽象類的基礎(chǔ)上,增加了對(duì)狀態(tài)有限性的控制,拓展與抽象,比枚舉更加靈活和方便了。
再例如前面網(wǎng)絡(luò)的封裝:
sealed class Result<out T : Any> {
data class Success<out T : Any>(val data: T) : Result<T>()
sealed class Error(val exception: Exception) : Result<Nothing>() {
class RecoverableError(exception: Exception) : Error(exception)
class NonRecoverableError(exception: Exception) : Error(exception)
}
object InProgress : Result<Nothing>()
}
通過Sealed Class可以很方便的對(duì)Error類型進(jìn)行拓展,同時(shí),增加新的狀態(tài)也非常簡(jiǎn)單,更重要的是,通過IDE的自動(dòng)補(bǔ)全功能,IDE可以自動(dòng)生成各個(gè)條件分支,避免人工編碼的遺漏。
向大家推薦下我的網(wǎng)站 https://xuyisheng.top/ 點(diǎn)擊原文一鍵直達(dá)
專注 Android-Kotlin-Flutter 歡迎大家訪問
往期推薦
更文不易,點(diǎn)個(gè)“三連”支持一下??
