Kotlin的密封(Sealed)类:超强的枚举(KAD 28)

作者:Antonio Leiva

时间:Jun 27, 2017

原文链接:https://antonioleiva.com/sealed-classes-kotlin/

 

 

Kotlin的封装类是Java中没有的新概念,并为此开辟了另一片可能性新的世界。

 

密封类允许你表达约束层次结构,其中对象只能是给定类型之一

 

 

也就是说,我们有一个具有特定数量的子类的类。最后,我们得到的结论是非常类似枚举的概念。所不同的是,在枚举中,我们每个类型只有一个对象;而在密封类中,同一个类可以拥有几个对象。

 

 

这种差异允许密封类的对象可以保持状态这给我们带来一些的优势(稍后会看到),它也为函数性概念敞开大门。

 

 

怎样使用密封类

 

 

 

实际上,实现密封类很简单。让我们来看一组能够应用于整数操作的例子。

 

 

实现情况如下:

 

1 sealed class Operation {
2     class Add(val value: Int) : Operation()
3     class Substract(val value: Int) : Operation()
4     class Multiply(val value: Int) : Operation()
5     class Divide(val value: Int) : Operation()
6 }

 

 

我们创建一个名为Operation的密封类,它包含四种操作:加法,减法,乘法和除法。

 

 

这一好处是,现在when表达式要求我们为所有可能的类型提供分支:

 

1 fun execute(x: Int, op: Operation) = when (op) {
2     is Operation.Add -> x + op.value
3     is Operation.Substract -> x - op.value
4     is Operation.Multiply -> x * op.value
5     is Operation.Divide -> x / op.value
6 }

 

 

如果你离开任何一个子类,when会抱怨其不会编译如果你实现它们,你不需要else语句。通常,由于我们确信我们对所有人都做正确的事情,不推荐这样做。

 

 

因为它会在编译时失败,并且不会运行,如果你决定添加新操作,这样做也非常好。现添加一对操作,增量和减量:

 

1 sealed class Operation {
2     ...
3     object Increment : Operation()
4     object Decrement : Operation()
5 }

 

 

现在,你会看到编译器警告你,存在一个问题。只需为这些新操作添加分支:

 

1 fun execute(x: Int, op: Operation) = when (op) {
2     ...
3     Operation.Increment -> x + 1
4     Operation.Decrement -> x - 1
5 }

 

 

你可能已经注意到我做了不同的事情。我使用对象而不是类。这是因为如果一个子类不保持状态,它只能是一个对象。你为该类创建的所有实例将完全相同,它们不能有不同的状态。

 

那么,在when表达式中,对那些情况你可以摆脱is。在这里,因为只有一个实例,你只能比较对象,你不需要检查对象的类型。如果为那些,你也可以保留is,它也能工作。

 

 

如果你仔细考虑一下,所有子类都是对象的密封类与枚举相同。

 

 

将副作用移到单点上

 

 

 

函数编程的副作用是一个非常通用概念。函数编程在很大程度上依赖于给定的功能,相同的参数将返回相同的结果

 

 

任何修改状态都可能会破坏这一假设。但是任何程序都需要更改状态,与输入/输出元素进行通讯等。因此,重要的是如何在我们的代码中发现这些操作,并很容易隔离到特定地方。

 

例如,在Android视图上实现的任何操作都被视为副作用,因为视图的状态修改,而函数不知道。

 

我们可以创建一个密封类,使我们能够对视图进行操作。基于这个概念,以前的例子:

 

 1 sealed class UiOp {
 2     object Show: UiOp()
 3     object Hide: UiOp()
 4     class TranslateX(val px: Float): UiOp()
 5     class TranslateY(val px: Float): UiOp()
 6 }
 7 
 8 fun execute(view: View, op: UiOp) = when (op) {
 9     UiOp.Show -> view.visibility = View.VISIBLE
10     UiOp.Hide -> view.visibility = View.GONE
11     is UiOp.TranslateX -> view.translationX = op.px
12     is UiOp.TranslateY -> view.translationY = op.px
13 }

 

 

记住:因为我们不需要不同的实例,没有状态的操作就可以是对象。

 

 

现在,你可以创建一个Ui对象,汇集要在视图上做的所有接口操作,直到我们需要时,才执行它。

 

 

我们将描述我们想要做什么,然后我们可以创建一个执行它们的组件:

 

1 class Ui(val uiOps: List = emptyList()) {
2     operator fun plus(uiOp: UiOp) = Ui(uiOps + uiOp)
3 }

 

 

Ui类存储操作列表,并指定一个累加和运算符,这将有助于使所有内容更清晰,更易于阅读。现在我们可以指定要执行的操作列表:

 

1 val ui = Ui() +
2         UiOp.Show +
3         UiOp.TranslateX(20f) +
4         UiOp.TranslateY(40f) +
5         UiOp.Hide
6 
7 run(view, ui)

 

 

 

然后运行它。这里我只是使用一个run函数,但如果需要,这可以是一个完整的类。

1 fun run(view: View, ui: Ui) {
2     ui.uiOps.forEach { execute(view, it) }
3 }

 

 

想象一下这些,现在你所做的一切都是按顺序运行的,但是这可能会很复杂。

 

 

run函数可以传递给另一个函数或类,并且那些操作的运行方式将是完全可互换的记住你可以将函数作为参数传递

 

 

结论

 

 

密封类的概念非常简单,但是如果您之前没有使用函数式编程,则需要一些使用新概念的基础。

 

 

我必须说,由于我在函数式编程方面的知识限制,我还没有最大限度的使用密封类。

 

 

如果您像我一样热衷于此,我建议你查看以前的文章,您可以在其中了解更多有关Kotlin信息,或者在本书中了解如何使用Kotlin从头开始创建一个完整的Android应用程序

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

val ui = Ui() +

        UiOp.Show +

        UiOp.TranslateX(20f) +

        UiOp.TranslateY(40f) +

        UiOp.Hide

 

run(view, ui)

posted @ 2017-07-02 20:23  figozhg  阅读(...)  评论(...编辑  收藏