通过官方项目来学习——枚举、密封类、密封接口的区别和使用场景

794 阅读4分钟

image.png

相信点进来愿意看这篇文章的jym都用过密封类、枚举类甚至也已经用到了密封接口,但是多多少少可能还是有点迷惑。写这篇文章就是希望能跟大家一起梳理这三类的区别,搞清楚在哪种情况用哪种实现最合适。

从java到kotlin,大家肯定对枚举已经比较熟悉了,这里就不专门说它了。但是密封类和密封接口的概念最先是在kotlin上实现的,java之前是没有的。那么我们可能会想,为什么在已经有枚举的情况下还要新增密封类和密封接口这两个新的概念出来呢?这三点又有什么不同?什么情况我们应该用他们呢?如果你没有一个清晰的答案,那么请带着这三个问题请继续看下去

下面用nowinandroid项目内的代码来当个例子

密封类

sealed class Icon {
    data class ImageVectorIcon(val imageVector: ImageVector) : Icon()
    data class DrawableResourceIcon(@DrawableRes val id: Int) : Icon()
}

这个密封类很简单。里面就封了两个数据类,两个类的参数类型不同。根据命名不难看出,Icon密封类是提供Icon给ui用的,ImageVectorIcon通过提供ImageVector实现icon显示,DrawableResourceIcon提供资源id来实现icon显示。那么很容易想象出来后面的使用场景无非就是通过一个类型判断,来执行对应条件下的加载就完了。这里大家只需留意一点,就是这个密封类我们是没有做任何初始化操作的。

枚举类

enum class TopLevelDestination(
    val selectedIcon: Icon,
    val unselectedIcon: Icon,
    val iconTextId: Int,
    val titleTextId: Int,
) {
    FOR_YOU(
        selectedIcon = DrawableResourceIcon(NiaIcons.Upcoming),
        unselectedIcon = DrawableResourceIcon(NiaIcons.UpcomingBorder),
        iconTextId = forYouR.string.for_you,
        titleTextId = R.string.app_name,
    ),
    BOOKMARKS(
        selectedIcon = DrawableResourceIcon(NiaIcons.Bookmarks),
        unselectedIcon = DrawableResourceIcon(NiaIcons.BookmarksBorder),
        iconTextId = bookmarksR.string.saved,
        titleTextId = bookmarksR.string.saved,
    ),
    INTERESTS(
        selectedIcon = ImageVectorIcon(NiaIcons.Grid3x3),
        unselectedIcon = ImageVectorIcon(NiaIcons.Grid3x3),
        iconTextId = interestsR.string.interests,
        titleTextId = interestsR.string.interests,
    ),
}

现在我们来简单看一眼这个枚举类。4个参数组成了构造参数,前两个参数的类型是上面定义的Icon密封类。然后定义了三个值For_You,BOOKMARKS,INTERESTS。这里需要注意了,这三个值的参数都并必须完成初始化提供实例才行,而前面定义的密封类并不需要。那么我们发现了一个枚举类跟密封类的区别了。这个区别我个人觉得也是枚举类和密封类最大的一个区别-复杂度。这里的枚举类的使用场景是给app的底部导航栏用的,我们知道一般导航栏需要的东西很简单数量也不多,一般都是一个选中时的icon,未选中时的icon,一个标题就完了,所以很简单一点都不复杂。后期我们要添加多少个新的也都是这个模版,很方便我们统一维护。但是这里的Icon密封类就相对要复杂灵活点了,首先可以实现提供显示icon的方法有还很多种,我们不太可能一次把所有的方式都写进去,所以我们可以通过Icon密封类随时扩展,再则这个icon可能是会经常更换的(比方说版本更新,配合活动动态更换等),那么我们这个资源肯定就不能写死了,也就是不推荐用枚举去实现,不然换一次icon就要新建一个新值,维护起来麻烦也不优雅。那么这个时候的密封类就又起到作用了,我们只用替换原有枚举类初始化的资源就行了。

小结一下:简单稳定的用枚举,复杂灵活的用密封

密封接口

密封接口这个概念并不是跟密封类一同出现的。是先有的密封类后面高版本kotlin才出现的密封接口。刚出来的时候,我也不懂为啥还要特意再整个密封接口出来,正好在掘金上看到了大佬写的一篇很好的文章介绍了密封接口和密封类的区别,这里我就不再复述了,建议大家直接点链接去学习 Kotlin 1.5 新特性:密封接口比密封类强在哪? - 掘金 (juejin.cn)。下面还是提供一下nowinandroid项目里面部分用到密封接口地方的代码。

package com.google.samples.apps.nowinandroid.core.result

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart

sealed interface Result<out T> {
    data class Success<T>(val data: T) : Result<T>
    data class Error(val exception: Throwable? = null) : Result<Nothing>
    object Loading : Result<Nothing>
}

用来封装网络请求返回的几种情况,可读性强于密封类实现

sealed interface NewsFeedUiState {
    /**
     * The feed is still loading.
     */
    object Loading : NewsFeedUiState

    /**
     * The feed is loaded with the given list of news resources.
     */
    data class Success(
        /**
         * The list of news resources contained in this feed.
         */
        val feed: List<UserNewsResource>,
    ) : NewsFeedUiState
}

封装页面加载的情况,可读性强于密封类实现