Kotlin密封类sealed

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/120496247
本文出自【赵彦军的博客】

文章目录

  • 简介
  • 密封类与枚举类对比
  • 创建状态集
  • 使用
  • 进一步简化
  • 举例

简介

密封类用来表示受限的类继承结构:当一个值为有限几种的类型, 而不能有任何其他类型时。

在某种意义上,他们是枚举类的扩展:枚举类型的值集合 也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。

声明一个密封类,使用 sealed 修饰类,密封类可以有子类。

sealed 不能修饰 interface ,`abstract classe(会报 warning,但是不会出现编译错误)

密封类与枚举类对比

① 相同点 ( 类型限制 ) : 从类型种类角度对比 , 类与枚举类类似 , 枚举类的值的集合是受限制的 , 不能随意扩展 ;

② 不同点 ( 对象个数限制 ) : 从每个类型对象个数对比 , 枚举类的每个类型只能存在一个实例 , 而密封类的每个类型可以创建无数个实例 ;

创建状态集

密封类声明:在 class 前添加 sealed 修饰符 , 即可将该类声明为密封类 ;

和抽象类类似,Sealed Class可用于表示层级关系。它的子类可以是任意的类:data class、普通Kotlin对象、普通的类,甚至也可以是另一个密封类,所以,我们定义一个Result Sealed Class:

 */
sealed class Result {
    //定义网络请求成功
    data class OK(val result: String) : Result()

    //定义网络请求失败
    data class FAIL(val throwable: Throwable) : Result()
}

当然,也不一定非要写在顶层类中:

//定义结果密封类
sealed class Result

//定义网络请求成功
data class OK(val result: String) : Result()

//定义网络请求失败
data class FAIL(val throwable: Throwable) : Result()

这样也是可以的,它们的区别在于引用的时候,是否包含顶层类来引用而已。

大部分场景下,还是建议第一种方式,可以比较清晰的展示调用的层级关系。

使用

接下来,我们来看下如何使用Sealed Class。

fun main() {
    //模拟封装枚举的产生
    val result = if (true) {
        Result.OK("Success")
    } else Result.FAIL(Exception("error"))

    when (result) {
        is Result.OK -> println(result.result)

        is Result.FAIL -> println(result.throwable)
    }
}

大部分场景下,Sealed Class都会配合when一起使用,同时,如果when的参数是Sealed Class,在IDE中可以快速补全所有分支,而且不会需要你单独补充else 分支,因为Sealed Class已经是完备的了。

所以when和Sealed Class真是天作之合。

进一步简化

其实我们还可以进一步简化代码的调用,因为我们每次使用Sealed Class的时候,都需要when一下,有些时候,也会产生一些代码冗余,所以,借助拓展函数,我们进一步对代码进行简化。

inline fun Result.doSuccess(success: (String) -> Unit) {
    if (this is Result.OK) {
        success(result)
    }
}

inline fun Result.doError(error: (Exception) -> Unit) {
    if (this is Result.FAIL) {
        error(throwable)
    }
}

我们在使用一下:

fun main() {
    //模拟封装枚举的产生
    val result = if (true) {
        Result.OK("Success")
    } else Result.FAIL(Exception("error"))


    result.doSuccess {

    }

    result.doError {

    }
}

是不是简单很多

举例

假如在 Android 中我们有一个 view,我们现在想通过 when 语句设置针对 view 进行两种操作:显示和隐藏,那么就可以这样做:

sealed class UiOp {
    object Show: UiOp()
    object Hide: UiOp()
} 
fun execute(view: View, op: UiOp) = when (op) {
    UiOp.Show -> view.visibility = View.VISIBLE
    UiOp.Hide -> view.visibility = View.GONE
}

以上功能其实完全可以用枚举实现,但是如果我们现在想加两个操作:水平平移和纵向平移,并且还要携带一些数据,比如平移了多少距离,平移过程的动画类型等数据,用枚举显然就不太好办了,这时密封类的优势就可以发挥了,例如:

sealed class UiOp {
    object Show: UiOp()
    object Hide: UiOp()
    class TranslateX(val px: Float): UiOp()
    class TranslateY(val px: Float): UiOp()
}

fun execute(view: View, op: UiOp) = when (op) {
    UiOp.Show -> view.visibility = View.VISIBLE
    UiOp.Hide -> view.visibility = View.GONE
    is UiOp.TranslateX -> view.translationX = op.px // 这个 when 语句分支不仅告诉 view 要水平移动,还告诉 view 需要移动多少距离,这是枚举等 Java 传统思想不容易实现的
    is UiOp.TranslateY -> view.translationY = op.px
}

以上代码中,TranslateX 是一个类,它可以携带多于一个的信息,比如除了告诉 view 需要水平平移之外,还可以告诉 view 平移多少像素,甚至还可以告诉 view 平移的动画类型等信息,我想这大概就是密封类出现的意义吧。

除此之外,如果 when 语句的分支不需要携带除“显示或隐藏view之外的其它信息”时(即只需要表明 when 语句分支,不需要携带额外数据时),用 object 关键字创建单例就可以了,并且此时 when 子句不需要使用 is 关键字。

最后,我们甚至可以把这一组操作封装成一个函数,以便日后调用,如下:

// 先封装一个UI操作列表
class Ui(val uiOps: List = emptyList()) {
    operator fun plus(uiOp: UiOp) = Ui(uiOps + uiOp)
}

// 定义一组操作
val ui = Ui() +
        UiOp.Show +
        UiOp.TranslateX(20f) +
        UiOp.TranslateY(40f) +
        UiOp.Hide
// 定义调用的函数
fun run(view: View, ui: Ui) {
    ui.uiOps.forEach { execute(view, it) }
}

run(view, ui) // 最终调用

你可能感兴趣的:(Kotlin实战指南,kotlin,java,密封类,sealed,赵彦军)