item 结构设计
kotlin
xxItem.kt:
/**
* list item
*/
interface IAlbumDetailsItem {
fun id(): String
fun name(): String?
fun author(): String?
fun vipContent(): Boolean // 是否是 vip 内容
fun playFlag(): Boolean // 是否可以播放
fun vipPlayFlag(): Boolean // 是否会员可以播放
fun getAlbumDetails(): IAlbumDetailsHead?
fun getPlayable(): IPlayable?
fun playerList(): MutableList<IPlayable>?
fun setHeadData(head: IAlbumDetailsHead?)
}
@ExcludeFromGeneratedCodeCoverage
data class AlbumDetailsItem(
val id: String,
val name: String?,
val author: String?,
val isPlayFlag: Boolean,
val authTrackPlayType: Int,
var data: IAlbumDetailsHead?,
val isVipContent: Boolean,
val iPlayable: IPlayable?,
val playerList: MutableList<IPlayable>? = null
) : IAlbumDetailsItem {
override fun id() = id
override fun name() = name ?: ""
override fun author() = author ?: ""
override fun playFlag(): Boolean = isPlayFlag
override fun vipPlayFlag(): Boolean = authTrackPlayType == ContentConstant.TYPE_TRACK_AUTH_VIP_ANY
override fun vipContent(): Boolean = isVipContent
override fun getAlbumDetails(): IAlbumDetailsHead? = data
override fun getPlayable(): IPlayable? = iPlayable
override fun playerList() = playerList
override fun setHeadData(head: IAlbumDetailsHead?) {
data = head
}
}
class ExploreItem private constructor(
val type: Int,
val route: String? = null,
val titleMore: TitleMore? = null,
val funcData: FuncData? = null,
val rank: IRankVO? = null,
val rsContent: IRecommendSegmentContent? = null,
val simulation: Simulation? = null,
var bannerWidget: BannerWidget? = null,
val bannerItemList: List<BannerItem>? = null,
override val itemType: Int = type
) : MultiItemEntity,
IAlbumDetailsItem {
.....
}
class PodcastItem private constructor(
val type: Type,
val route: String? = null,
val titleString: TitleMore? = null,
val albumItem: IAlbumItem? = null,
val artist: IArtist? = null,
val playableItem: PlayableItem? = null,
var isPlaying: Boolean? = null,
val order: Int = 0,
override val itemType: Int = type.getType()
) : MultiItemEntity,
IAlbumDetailsItem {
.....
}
class SearchComplexItem private constructor(
val type: Type,
val id: String? = null,
val name: String? = null,
val author: String? = null,
val cover: String? = null,
val playCount: String? = null,
val title: Int? = null,
val order: Int? = null,
val index: Int? = null,
val iPlayable: IPlayable? = null,
var isSelect: Boolean = false,
var isPlaying: Boolean = false,
override val itemType: Int = type.getType()
) : MultiItemEntity,
IAlbumDetailsItem {
.......
}
IAlbumDetailsItem 接口设计架构深度解析
概述
设计 IAlbumDetailsItem 接口,接口内全部声明为函数方法,是典型多态架构方案,也是车机复杂复合多类型列表(MultiType)业务解耦的标准示范。下文从接口设计必要性 、全部使用函数而非属性的优势两个维度完整拆解该设计思路。
一、为什么需要定义 IAlbumDetailsItem 接口
接口本质是在UI视图层与异构业务数据源之间,定义一层统一视图契约(View Contract),核心解决车机列表多类型渲染痛点。
1. 彻底解决MultiType复合列表类型爆炸问题
车机专辑详情页列表属于混合复合列表,列表内会混合多种完全不同业务实体:
- 普通单曲条目
AlbumDetailsItem - 运营推荐区块
ExploreItem - 播客单集条目
PodcastItem - 搜索联动结果条目
SearchComplexItem
不使用接口的弊端
列表只能定义为 List<Any>,列表渲染时需要大量 is 类型判断+强转代码:
kotlin
if (item is AlbumDetailsItem) {
// 单曲渲染逻辑
} else if (item is PodcastItem) {
// 播客渲染逻辑
} else if (item is SearchComplexItem) {
// 搜索条目渲染逻辑
}
每新增一种列表类型,都需要修改Adapter判断分支,严重违反开闭原则,代码臃肿、维护成本极高。
引入接口后的优势
列表统一声明为 List<IAlbumDetailsItem>,所有业务模型仅需实现该接口即可接入列表渲染。依靠多态分发,Adapter无需关心底层具体实体类型,直接调用接口统一方法渲染,新增条目类型无需改动Adapter核心逻辑。
2. 实现车机公共自定义View跨页面复用
SearchComplexItem、PodcastItem 分布在搜索、播客、专辑详情等独立业务模块,但车机存在全局统一公共列表组件(如车载媒体条目通用View)。
公共自定义View仅依赖接口做数据绑定,与具体业务模型完全解耦:
kotlin
fun bindData(item: IAlbumDetailsItem) {
titleTextView.text = item.name()
vipIcon.visibility = if(item.vipContent()) View.VISIBLE else View.GONE
}
一套通用View可在全项目所有复合列表页面复用。
二、接口全部使用函数fun,而非属性val/var的设计考量
Kotlin接口同时支持定义属性与函数,该方案统一采用函数声明,是兼顾异构数据兼容、跨语言兼容、内存优化、业务扩展性的防御性编程设计。
1. 灵活兼容异构数据源,不受实体内部字段命名限制
各业务模型后端返回字段、内部存储结构差异极大:
- 单曲模型:直接存在顶层字段
val name: String? - 播客模型:名称嵌套在内部子对象
albumItem: IAlbumItem?,无顶层name字段 - 搜索条目:顶层字段
val name: String?
若使用接口属性 val name: String?:
Kotlin语法强制实现类必须存在同名可重写字段,嵌套结构模型无法优雅适配映射。
若使用接口函数 fun name(): String?:
函数代表数据获取能力,不限制实体内部数据存储结构,各实现类可自由自定义取值逻辑,适配嵌套、二次计算等场景:
kotlin
// 播客条目实现示例
override fun name(): String? = albumItem?.getAlbumName()
// 搜索条目实现示例
override fun name(): String? = name
2. 减少字节码冗余,优化Java混编内存开销
车机项目存在大量Java底层代码(蓝牙服务、播放器封装、车载系统服务):
- Kotlin接口定义
val name: String时,编译后会自动生成隐式幕后字段、getter、setter,字节码冗余; - 纯函数
fun name()编译后仅生成标准Java接口方法,无额外字段内存占用;
车载列表常一次性加载上百条数据,大量实体对象可显著降低堆内存占用。
3. 支持运行时动态实时计算,拓展性更强
接口属性的值在对象实例化时固定,无法动态变更;
函数支持每次调用实时计算、动态返回结果,适配车载实时业务场景:
kotlin
override fun playFlag(): Boolean {
// 实时结合车速、网络状态动态判断播放权限
return basePlayFlag && NetworkUtils.isAvailable() && CarSpeedManager.getSpeed() < 80
}
为后续新增车载动态业务逻辑预留扩展空间。
三、架构总结
该设计充分利用面向对象封装、多态特性,是分层解耦的优秀实践:
- 接口分层隔离 :通过
IAlbumDetailsItem统一视图层契约,抹平单曲、播客、推荐、搜索等异构实体的展示差异,列表组件无需感知底层业务模型; - 函数契约解耦:放弃固定属性,以"行为/能力"定义接口标准,解放各实体内部数据结构限制,同时兼顾Java混编性能、动态业务扩展;
- 遵循开闭原则:新增列表条目类型仅需实现接口,无需修改Adapter、公共View渲染逻辑,长期维护成本极低。