用途
该技能介绍一种 Kotlin 代码组织模式:用一个不含任何成员的「标记接口」(tag interface)给互不相关的类打标签,使持久化实体、网络传输结果、功能代码本地创建的记录等异构类型能够出现在同一个列表中,并被同一个渲染器(例如一个 @Composable 函数)接受和渲染。当渲染器确实需要按类型分支时,标签上再附加一个嵌套枚举和一个访问函数,充当运行时判别器。
主要能力
- 用空接口做统一入口:示例中定义
interface HomeContentType与interface LibraryType,一个渲染器@Composable fun ContentTile(data: HomeContentType)即可服务两个屏幕。 - 多继承作为关键机制:一个标签可以继承另外两个标签,使其所有实现类同时可被两个屏幕的渲染器渲染,而两个屏幕的列表类型无需互相知晓。
- 嵌套枚举判别器:例如
interface PlaylistType : HomeContentType, LibraryType内声明enum class Type { REMOTE, RADIO, LOCAL, ALBUM, SERIES }和fun playlistType(): Type,需要分支时用它区分。 - 选型指导:当实现类无法共存于同一模块时选标签接口;当所有实现类可放在一起时,sealed 层级能换取穷尽性检查,更值得。
使用方式与已验证的陷阱
正文以「陷阱」章节形式给出实践要点,均基于资料原文:
- 失去穷尽性检查:对标签接口的
when永远无法穷尽,必须写else;且渲染器每读取一个字段就要写一次这样的when。新增实现类时所有分支同时落入else,编译通过但渲染为空白。建议把各字段分支集中在一个返回小型载体对象的when中,使遗漏只形成一个空洞。 - 判别器必须完备:某个标签声明了 SONG、ALBUM、ARTIST、PLAYLIST,而一个 series 实体实现它却没有自己的枚举成员,访问函数便返回 PLAYLIST,编译通过、静默错误且下游无法恢复。新增实现类时应重新检查枚举。
- 两个标签的访问函数不能同名:各自嵌套枚举是不同类型,同名同参数列表不同返回类型意味着一个类最多只能实现其中一个。建议让访问函数名派生自标签名。
- 标签名会与其他类型冲突:同名概念的类型在同一文件导入时需要
import … as alias。建议按其在列表中的角色而非概念来命名标签。 as?加?: return@items会让条目静默消失:不产生行、不产生日志、不产生失败。应改为渲染可见的回退分支,或至少上报一次未处理类型。filterIsInstance<Tag>()会静默丢弃:在列表本应同质时,应在过滤前后比较大小。- 标签会引入同名类型:来自不同包的多个
Content类无法同时导入,分支只能写全限定名is some.package.Content ->,这是标签正常工作的表现,但应保持分支顺序一致。
验证方法
资料给出用 grep 验证的命令思路:列出某标签的所有实现类并查看各自返回的枚举值;分别检查判别器是否完备(每个枚举成员都有实现类回答)以及每个成员是否真的被分支使用;再检查是否存在多个标签竞争同一个访问函数名(grep -rhoE "fun [a-zA-Z][A-Za-z0-9_]*\(\): Type" --include="*.kt" . | sort | uniq -c | sort -rn,计数大于一即为冲突)。原命令需保留原样使用。
适用场景
多模块 Kotlin 项目中,需要将来源不同的类统一进一个列表并用一个渲染器呈现,或需要在不反转既定分层依赖方向的前提下实现跨屏幕复用渲染逻辑。
限制
该模式放弃编译期穷尽性检查,错误只能在运行时或通过上述 grep 检查发现;判别器遗漏、访问函数同名、标签与同名概念类型冲突等约束需要人工维护。