公众号:ByteCode,致力于分享最新技术原创文章,涉及 Kotlin、Jetpack、译文、系统源码、 LeetCode / 剑指 Offer / 多线程 / 国内外大厂算法题 等等一系列文章。
在之前的文章中我也分析过 Sealed Classes 原理,以及 Google 和很多开源项目为什么都在大量的使用它,如果你对 Sealed Classes 还不是很了解,可以前往查看 Kotlin Sealed 是什么?为什么 Google 都在用 主要内容如下:
- Sealed Classes 原理分析?
- 枚举和抽象类都有那些局限性?
- 为什么枚举可以作为单例?枚举作为单例有那些优点?
- 分别在什么情况下使用枚举和 Sealed Classes?
- Sealed Classes 究竟是什么?
- 为什么 Sealed Classes 用于表示受限制的类层次结构?
- 为什么说 Sealed Classes 是枚举类的扩展?
- Sealed Classes 的子类可以表示不同状态的实例,那么在项目中如何使用?
- 禁止在 Sealed Classes 所定义的文件外使用, Kotlin 是如何做到的呢?
而今天这篇文章,我们主要从类层次结构来讨论一下 Sealed Classes(密封类) 和 Tagged Classes(标记类)的优缺点。在开始分析之前,我们先介绍一下什么是 Tagged Classes(标记类)以及都有那些缺点。
Tagged Classes 是什么
在一个类中包含一个指示操作的标记字段或者特征,方便在它们之间切换的类称为 Tagged Classes(标记类),在 Effective Java 中也指出了 Tagged Classes 存在很多问题,这里引用 Effective Java Item 23 中的一个案例来分析 Tagged Classes 存在的问题,这里用 Kotlin 重写了。
class Figure( // 这个标签字段:用来表示图形的形状 val shape: Shape, // 这个字段用于圆形 val radius: Double = 0.0, // 这两个字段用于矩形 val length: Double = 0.0, val width: Double = 0.0 ) { // 定义了两个形状 矩形、圆形 enum class Shape { RECTANGLE, CIRCLE } // 计算当前图形的面积 fun area(): Double = when (shape) { Shape.RECTANGLE -> length * width Shape.CIRCLE -> Math.PI * (radius * radius) else -> throw AssertionError(shape) } companion object { fun createRectangle(radius: Double) { Figure( shape = Shape.RECTANGLE, radius = radius ) } fun createCircle(length: Double, width: Double = 0.0) { Figure( shape = Shape.CIRCLE, length = length, width = width ) } } }
正如你所见,代码中包含了很多模板代码,包括标记字段、切换语句、枚举等等,在一个类中包含了很多不同的操作,如果以后增加新的操作,有需要增加新的标记,实际情况这样的代码在项目中非常的常见,主要存在以下几个问题:
- 增加了很多模板代码
- 内存是非常稀缺的资源,当我们创建圆形的时候,与它无关的字段也要保留,增加当前类所占用的内存
- 降低了代码的可读性,类中混合了很多操作例如枚举、切换语句等等,为了保证对象正确的创建,通常需要用到工厂模式等等设计模式
- 如果增加新的图形,不得不去修改原有的代码结构
- ......
那么有没有很好的替换方案,可以解决以上所有的问题,而且还可以在不修改原有的代码结构基础上增加新的图形,这就需要用到类的层次结构。
类的层次结构
无论是 Java 还是 Kotlin 我们都会使用类的层次结构代替标记类,而在 Kotlin 中我们常用 Sealed Classes 表示受限制的类层次结构, 在之前的文章 Kotlin Sealed 是什么? 中已经详细分析过 Sealed Classes。接下来一起来看一下如何使用 Sealed Classes 优化上面的代码。
sealed class Figure { abstract fun area(): Double class Rectangle(val length: Double, val width: Double) : Figure() { override fun area(): Double = length * width } class Circle(val radius: Double) : Figure() { override fun area(): Double = Math.PI * (radius * radius) } }
正如你所见,代码简洁干净了很多,不包含模板代码,并且类之间的职责分明,提高了代码的灵活性,完美的解决了上述所有的缺点。每个类中不包含无关的字段,同时在类中添加新的参数,并不会影响其他类。
如果我们需要增加新的图形,只需要新增加一个类即可,并不会破坏原有的代码结构,例如这里我们增加一个球形。
class Ball(val radius: Double) : Figure() { override fun area(): Double = 4.0 * Math.PI * Math.pow(radius, 2.0) }
不仅仅如此,Sealed Classes 结合 when 表达式一起使用会更加的方便,when 语句下的所有分支可以通过快捷键 Mac/Win/Linux:Alt + Enter
自动生成,如下所示。
fun Figure.Valida() { when (this) { is Figure.Ball -> { println("I am Ball") area() } is Figure.Circle -> { println("I am Circle") area() } is Figure.Rectangle -> { println("I am Rectangle") area() } } }
在 Effective Java 中也说明了 Tagged classes(标记类)很少有适合的场景,但是往往在开发过程中,为了快速的开发一个功能,往往会忽略它所带来的影响,但是我们在做优化的时候,遇到这种 Tagged classes 是否可以考虑使用类的层次结构来代替,如果是 Kotlin 建议使用 Sealed Classes。
参考文章
- Effective Java Item 23: Prefer class hierarchies to tagged classes
- Effective Kotlin Item 40: Prefer class hierarchies to tagged classes
全文到这里就结束了,如果有帮助 点个赞 就是对我最大的鼓励
代码不止,文章不停
欢迎关注公众号:ByteCode,持续分享最新的技术
最后推荐我一直在更新维护的项目和网站:
- 计划建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,欢迎前去查看:AndroidX-Jetpack-Practice
- LeetCode / 剑指 offer / 国内外大厂面试题 / 多线程 题解,语言 Java 和 kotlin,包含多种解法、解题思路、时间复杂度、空间复杂度分析
- 最新 Android 10 源码分析系列文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,仓库持续更新,欢迎前去查看 Android10-Source-Analysis
- 整理和翻译一系列精选国外的技术文章,每篇文章都会有译者思考部分,对原文的更加深入的解读,仓库持续更新,欢迎前去查看 Technical-Article-Translation
- 「为互联网人而设计,国内国外名站导航」涵括新闻、体育、生活、娱乐、设计、产品、运营、前端开发、Android 开发等等网址,欢迎前去查看 为互联网人而设计导航网站
历史文章
- LeetCode 从 0 到 200 学到了什么
- Android 12 行为变更,对应用产生的影响
- 图解多平台 AndroidStudio 技巧(三)
- 为数不多的人知道的 Kotlin 技巧以及 原理解析(一)
- 为数不多的人知道的 Kotlin 技巧以及 原理解析(二)
- Jetpack 最新成员 AndroidX App Startup 实践以及原理分析
- Jetpack成员Paging3获取网络分页数据并更新到数据库中(三)
- Jetpack 新成员 Hilt 与 Dagger 大不同(三)落地篇
- 全方面分析 Hilt 和 Koin 性能
- 神奇宝贝(PokemonGo) 眼前一亮的 Jetpack + MVVM 极简实战
- Kotlin Sealed 是什么?为什么 Google 都用
- Kotlin StateFlow 搜索功能的实践 DB + NetWork
- [Google] 再见 SharedPreferences 拥抱 Jetpack DataStore
- Kotlin 插件的落幕,ViewBinding 的崛起
- 竟然如此简单,DataBinding 和 ViewBinding