作者:Antonio Leiva

时间:Jun 27, 2017

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

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

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

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

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

怎样使用密封类

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

实现情况如下:

 sealed class Operation {
class Add(val value: Int) : Operation()
class Substract(val value: Int) : Operation()
class Multiply(val value: Int) : Operation()
class Divide(val value: Int) : Operation()
}

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

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

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

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

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

 sealed class Operation {
...
object Increment : Operation()
object Decrement : Operation()
}

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

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

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

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

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

将副作用移到单点上

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

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

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

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

 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
is UiOp.TranslateY -> view.translationY = op.px
}

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

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

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

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

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

 val ui = Ui() +
UiOp.Show +
UiOp.TranslateX(20f) +
UiOp.TranslateY(40f) +
UiOp.Hide run(view, ui)

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

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

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

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

结论

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

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

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

val ui = Ui() +

UiOp.Show +

UiOp.TranslateX(20f) +

UiOp.TranslateY(40f) +

UiOp.Hide

run(view,
ui)

Kotlin的密封(Sealed)类:超强的枚举(KAD 28)的更多相关文章

  1. Kotlin——中级篇(五):枚举类(Enum)、接口类(Interface)详解

    在上一章节中,详细的类(class)做了一个实例讲解,提到了类(class)的实例化.构造函数.声明.实现方式.和Java中类的区别等.但是对于Kotlin中的类的使用还远远不止那些.并且在上文中提到 ...

  2. 在ASP.Net Core 中使用枚举类而不是枚举

    前言:我相信大家在编写代码时经常会遇到各种状态值,而且为了避免硬编码和代码中出现魔法数,通常我们都会定义一个枚举,来表示各种状态值,直到我看到Java中这样使用枚举,我再想C# 中可不可以这样写,今天 ...

  3. Java自学-类和对象 枚举类型

    枚举类型 步骤 1 : 预先定义的常量 枚举enum是一种特殊的类(还是类),使用枚举可以很方便的定义常量 比如设计一个枚举类型 季节,里面有4种常量 public enum Season { SPR ...

  4. C++类中的枚举类型

    在看effective c++的时候,其中第二条边指出.尽量使用const ,enum代替define.在写程序的时候,需要入参为设备类型,第一反应是枚举一个设备类型,并以名字命名.但是有一个问题挺困 ...

  5. java用普通类如何实现枚举功能

    用普通类如何实现枚举功能,定义一个Weekday的类来模拟枚举功能.     1.私有的构造方法.     2.每个元素分别用一个公有的静态成员变量表示.      可以有若干公有方法或抽象方法.采用 ...

  6. Java常用类:包装类,String,日期类,Math,File,枚举类

    Java常用类:包装类,String,日期类,Math,File,枚举类

  7. kotlin中的嵌套类与内部类

    Java中的内部类和静态内部类在Java中内部类简言之就是在一个类的内部定义的另一个类.当然在如果这个内部类被static修饰符修饰,那就是一个静态内部类.关于内部类 和静态内部类除了修饰符的区别之外 ...

  8. Kotlin enum class 匿名类实例

    Kotlin里的枚举类里有新玩意:就是枚举类的常量可以同时看成是一个同名匿名类 既然是类就可以与方法关联 看看官网的代码 如果你有过其它语言的使用枚举的经历,你可能对这个定义和说明很迷惑 我给你一个例 ...

  9. 【Python】[面向对象高级编程] 多成继承,定制类,使用枚举

    1.多成继承 class SmallDog(Animal,Dog) pass MixIn就是一种常见的设计. 2.定制类类似__slots__这种形如 __xxx__ 的变量或者函数名,在python ...

随机推荐

  1. Windows 下制作CentOS7安装U盘

    本文属于另类的U盘制作方法(更多U盘安装见U盘安装CentOS ),如何安装CentOS,请参考<安装指南> 以下列出了,完整的制作步骤: 1.下载安装镜像 选择一个合适的镜像网站,比如网 ...

  2. Lambda使用

    说明 本文内容来自 [Java8 In Action] 一书 四种方法引用类型 类型 示例 引用静态方法 ContainingClass::staticMethodName 引用某个对象的实例方法 c ...

  3. 二十五、详述 IntelliJ IDEA 提交代码前的 Code Analysis 机制

    在我们用 IntelliJ IDEA 向 SVN 或者 Git 提交代码的时候,IntelliJ IDEA 提供了一个自动分析代码的功能,即Perform code analysis: 如上图所示,当 ...

  4. js和jquery对象的相互转换

    在使用jquery的过程中发现很多需要将jquery对象转成js对象的例子. Query 对象是通过 jQuery 包装DOM 对象后产生的对象.jQuery 对象是 jQuery 独有的,其可以使用 ...

  5. tomcat如何配置俩个版本

    Java-web除了JDK,还需配置服务器(tomcat); 如何配置俩个版本的tomcat; 1.将tomcat-bin目录下的startup.bat和catalina.bat里的%CATALINA ...

  6. css:文章标题过长时,使用省略号

    html代码 <ul> <li><a href="" target="_blank">我是文章1,现在标题过长,使用css加 ...

  7. Web移动端商城 移动端商城手机网站html整套模板,web移动商城仿app手机模板下载

    --Web移动端商城移动端商城手机网站html整套模板,web移动商城仿app手机模板下载.原生的js和jquery-1.6.2.min.js,页面才有html5自适应.包括首页(轮播,导航).兼职( ...

  8. Redis(七):Redis的发布订阅

    Redis的发布订阅导航目录: 是什么 命令 案例 是什么 进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息. 订阅/发布消息图 命令 案例 先订阅后发布后才能收到消息,1 ...

  9. 虚拟机下linux 系统网卡配置、固定IP地址

    1.进入该目录下修改内容 vi       /etc/sysconfig/network-scripts/  ifcfg-eth0 TYPE=Ethernet BOOTPROTO=static DEF ...

  10. PHP基础4--函数-数组

    主要 函数 数组 常用系统函数 函数 基础 1)定义 function 函数名([$形参1],[$形参2],.....) { //函数体 } 点击查看函数定义形式 2) 调用 函数名([$实参1][, ...