DslAdapter开发简介
DslAdapter是一个Android RecyclerView的Adapter构建器, DSL语法, 面向组合子设计. 专注类型安全, 所有代码采用Kotlin编写.
为什么要开发DslAdapter
实际上在DslAdapter开始开发的时点已经有很多RecyclerAdapter的扩展库存在了, 有的甚至从2015年开始已经持续开发到了现在. 从功能上来说, 这些库的各项功能都非常成熟了, 几乎所有的需求都有涉及; 而从思想上来说, 各种构建方式都有相应的库
以现在很常见的库举例:
-
CymChad/BaseRecyclerViewAdapterHelper 1w6 star
这是Github上检索出Star最多的RecyclerAdapter的库, 它支持添加Item事件
、添加列表加载动画
、添加头部、尾部
、树形列表
等等,甚至设置空布局
-
FastAdapter
这个库是以前项目中也使用过的库, 功能也相当丰富, 相比上面的库更注重List的功能, 是一个从2015年开始开发一直持续维护的库 -
KidAdapter
kotlin编写,DSL语法的构建器,利用了kotlin的语法特性,构建上更简单,同时也实现了types功能。但功能上相比上面的库就简单很多了
甚至我多年前也已经写过一个相关库AdapterRenderer,也实现了很多功能。可以说RecyclerAdapter领域是最难以有新突破的地方了,似乎能做的只是在现有基础上小修小改而已了。
但现有的库真的已经完美到这种程度了吗?
不,现有的库也有很多的缺点:
- 非强类型,为了兼容多类型而直接忽略数据类型信息,失去了编译期类型检查,只能靠编写时自我约束
- 繁琐的模板代码。有些库会需要继承基础Adapter或者基础Holder继承方法来实现功能,因此会写大量的XXAdapter、XXItem
- 逻辑代码被迫分离。也是由于需要单独写XXAdapter、XXItem,而这些小类抽象度低,该界面的逻辑被迫分离到了这些小类中,即使是业务联系很大的逻辑
- 功能过于繁杂,抽象度低。功能上虽然丰富,但实际上包括了很多并不是RecyclerView应该关注的功能,导致变成了一个
全功能的Adapter
,Adapter的逻辑结构极其复杂,难以维护也难以扩展 - 类中变量和算法混杂,需要时常注意状态的同步问题
正因如此,为了解决以上这些问题,让我们编写的Adapter更简单、更安全,从而有了开发一个新库的想法
一个新的Adapter库应该是什么样的
想要构建一个Adapter的库,首先我们要想想我们这个新库应该是干什么的,这就需要回到RecyclerView这个库中Adapter被定义为什么。
RecyclerAdapter被定义为数据适配器
,即将数据
和视图
进行绑定:
数据 --映射--> 视图List
而RecyclerView之所以很强大是因为它已经不仅仅是用于显示List,它会需要显示复杂的视图结构,比如树状图、可展开列表等等
数据 --映射--> 复杂结构 --渲染--> 视图List
我们的Adapter库需要完成的工作简单来说就是:将数据变换为复杂的抽象结构(比如树状),再将复杂的抽象结构渲染为视图List(因为RecyclerView最终只支持平整单列表)
开始构建
定义基本组合子
实际我们需要实现的是一个变换问题,无论最终我们需要的抽象结构是简单的List还是复杂的树状图本质上都只是做这么一个数据的变换,因此这个变换函数
就是我们的基础组合子
,我们可以通过基础组合子的相互组合实现复杂功能
这个基本组合子就是DslAdapter库中的BaseRenderer
类:
interface Renderer<Data, VD : ViewData<Data>> {
fun getData(content: Data): VD
fun getItemId(data: VD, index: Int): Long = RecyclerView.NO_ID
fun getItemViewType(data: VD, position: Int): Int
fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
fun bind(data: VD, index: Int, holder: RecyclerView.ViewHolder)
fun recycle(holder: RecyclerView.ViewHolder)
}
它包含作为RecyclerAdapter基础组合子需要的几个基本方法。
数据放在哪儿
函数范式中副作用
是要严格分离的,而变量
就是一种副作用,如果允许变量在Renderer中不受管制的存在会使Renderer组合子本身失去可组合性,同时数据也变得很不可靠(线程不安全、Renderer并不保证只在一个地方使用一次)
而可以看到Renderer的基础方法中定义的都是纯函数,并且不包含允许副作用存在的IO
等类型(这里的IO
不同于Java中的input/output,而是指Haskell中的IO类型类),因此数据被设计为与Renderer严格分离(数据与算法的严格分离),ViewData即是这个被分离的数据
interface ViewData<out OriD> : ViewDataOf<OriD> {
val count: Int
val originData: OriD
}
Adapter设计
根据前一章的描述,我们构建的Renderer就是一个映射函数,因此Adapter也只需要使用这个映射函数将数据进行渲染即可
class RendererAdapter<T, VD : ViewData<T>>(
val initData: T,
val renderer: BaseRenderer<T, VD>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val dataLock = Any()
private var adapterViewData: VD = renderer.getData(initData)
...
override fun getItemCount(): Int = adapterViewData.count
override fun getItemViewType(position: Int): Int =
renderer.getItemViewType(adapterViewData, position)
override fun getItemId(position: Int): Long =
renderer.getItemId(adapterViewData, position)
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): RecyclerView.ViewHolder =
renderer.onCreateViewHolder(parent, viewType)
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) =
renderer.bind(adapterViewData, position, holder)
override fun onFailedToRecycleView(holder: RecyclerView.ViewHolder): Boolean {
renderer.recycle(holder)
return super.onFailedToRecycleView(holder)
}
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
renderer.recycle(holder)
}
}
可以看到Adapter实际只是将Renderer中的各个函数实际应用于原生RecyclerView.Adapter
的各个方法中即可,极其简单
另外,ViewData
只保存于Adapter中一份,它其实就包含整个Adapter的所有状态,换句话说只要保存它,可以完全恢复RecyclerView的数据状态;另外ViewData
是被锁保护起来的,保证数据的线程安全性
定义基础Renderer组合子
Renderer被我们定义为基础组合子,那我们需要哪些Renderer呢:
- EmptyRenderer: 空Renderer, count为0
- LayoutRenderer: 与View绑定的末端Renderer, 可自定义数量
- ConstantItemRenderer: 将常量绑定到View的末端Renderer, 可适配任意数据源, 可自定义数量
-
MapperRenderer: 转换目标Renderer的数据源类型, 一般通过
mapT()
来使用它 - ListRenderer: 将目标Renderer转换为适配列表数据源
-
SealedItemRenderer: 根据数据源具体数据选择不同的Renderer渲染, 比如对于
Int?
类型,可以在为null
的时候用EmptyRenderer渲染; 不为null
的时候使用LayoutRenderer渲染 - ComposeRenderer: 组合多个不同Renderer
- DataBindingRenderer : Android Databinding支持的Renderer
复杂的结构基本都可以通过组合他们来实现:
val adapter = RendererAdapter.multipleBuild()
.add(layout<Unit>(R.layout.list_header))
.add(none<List<Option<ItemModel>>>(),
optionRenderer(
noneItemRenderer = LayoutRenderer.dataBindingItem<Unit, ItemLayoutBinding>(
count = 5,
layout = R.layout.item_layout,
bindBinding = { ItemLayoutBinding.bind(it) },
binder = { bind, item, _ ->
bind.content = "this is empty item"
},
recycleFun = { it.model = null; it.content = null; it.click = null }),
itemRenderer = LayoutRenderer.dataBindingItem<Option<ItemModel>, ItemLayoutBinding>(
count = 5,
layout = R.layout.item_layout,
bindBinding = { ItemLayoutBinding.bind(it) },
binder = { bind, item, _ ->
bind.content = "this is some item"
},
recycleFun = { it.model = null; it.content = null; it.click = null })
.forList()
))
.build()
以上Adapter可图示为:
|--LayoutRenderer header
|
|--SealedItemRenderer
| |--none -> LayoutRenderer placeholder count 5
| |
| |--some -> ListRenderer
| |--DataBindingRenderer 1
| |--DataBindingRenderer 2
| |--...
技术细节
特征类型
上面说到Renderer被定义为:Renderer<T, VD : ViewData<T>>
, 其中ViewData因为和Renderer是强绑定的,所以往往一种ViewData和一种Renderer是一一对应的,在类型上ViewData和Renderer是相等的
用法上来说可以这样来看:类型Renderer<T, VD : LayoutViewData<T>>
可以被等价看待为LayoutRenderer<T>
, 相同的道理, 由于Updater和ViewData也是一一对应的关系, 因此类型Updater<T, VD : LayoutViewData<T>>
可以被等价看待为LayoutUpdater<T>
因此类型LayoutViewData<T>
可以看做LayoutXX系列所有类的一个特征类型
:通过这个类型可以唯一地识别出其对应的原始类型
在高阶类型的Java类型系统实现中也使用了类似的手法:
可以注意到VIewData的原始定义中继承了ViewDataOf
类型,这个类型原始定义是这样的:
class ForViewData private constructor() { companion object }
typealias ViewDataOf<T> = Kind<ForViewData, T>
@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
inline fun <T> ViewDataOf<T>.fix(): ViewData<T> =
this as ViewData<T>
其中ForViewData
就是我们定义的特征类型
,在fix()
函数中我们通过识别Kind<ForViewData, T>
中的ForViewData部分即可识别为其唯一对应的ViewData<T>,从而安全地进行类型转换
注意,特征类型
只是用于类型系统识别用,代码中我们实际并不会使用它的实例,因此可以看到上面定义的ForViewData
类型是私有构造函数,无法被实例化
另外,实际只要是具有唯一性
任意类型都可以作为特征类型
,可以利用已有的类型(比如LayoutViewData
)、也可以单独定义(比如ForViewData
)
利用特征类型
我们可以更灵活使用类型(见Kotlin与高阶类型
),也可以简化冗余的类型信息
比如之前版本的DslAdapter中,Adapter的泛型信息为:<T, VD : ViewData<T>, BR : BaseRenderer<T, VD>>
包含了T
、VD
和BR
三个类型信息,但根据我们上面的分析,VD
和BR
两个类型实际是等价的,他们包含了相同的类型特征,因此我们可以将其简化为<T, VD : ViewData<T>
,两个泛型即可保留所有我们需要的类型信息
对于复杂的Adapter这种简化对于减少编译系统压力来说的是更加明显的:
比如原来的类型有6000多字符:
ComposeRenderer<HConsK<ForIdT, Pair<ED, SearchConditions>, HConsK<ForIdT, Pair<ED, SearchConditions>, HNilK<ForIdT>>>, HConsK<ForComposeItem, Pair<Pair<ED, SearchConditions>, SealedItemRenderer<Pair<ED, SearchConditions>, HConsK<Kind<ForSealedItem, Pair<ED, SearchConditions>>, Pair<List<Pair<ED, CategoryInfo>>, ListRenderer<List<Pair<ED, CategoryInfo>>, Pair<ED, CategoryInfo>, MapperViewData<Pair<ED, CategoryInfo>, HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, ComposeViewData<HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, HConsK<ForComposeItemData, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HConsK<ForComposeItemData, Pair<CategoryInfo, SealedItemRenderer<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyRenderer<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingRenderer<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItemData>>>>>, MapperRenderer<Pair<ED, CategoryInfo>, HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, ComposeViewData<HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, HConsK<ForComposeItemData, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HConsK<ForComposeItemData, Pair<CategoryInfo, SealedItemRenderer<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyRenderer<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingRenderer<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItemData>>>>, ComposeRenderer<HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, HConsK<ForComposeItem, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HConsK<ForComposeItem, Pair<CategoryInfo, SealedItemRenderer<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyRenderer<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingRenderer<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItem>>>, HConsK<ForComposeItemData, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HConsK<ForComposeItemData, Pair<CategoryInfo, SealedItemRenderer<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyRenderer<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingRenderer<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItemData>>>>>>>, HConsK<Kind<ForSealedItem, Pair<ED, SearchConditions>>, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HNilK<Kind<ForSealedItem, Pair<ED, SearchConditions>>>>>>>, HConsK<ForComposeItem, Pair<Pair<ED, SearchConditions>, MapperRenderer<Pair<ED, SearchConditions>, List<Pair<ED, NormalCategoryInfo>>, ListViewData<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>>, HNilK<ForComposeItem>>>, HConsK<ForComposeItemData, Pair<Pair<ED, SearchConditions>, SealedItemRenderer<Pair<ED, SearchConditions>, HConsK<Kind<ForSealedItem, Pair<ED, SearchConditions>>, Pair<List<Pair<ED, CategoryInfo>>, ListRenderer<List<Pair<ED, CategoryInfo>>, Pair<ED, CategoryInfo>, MapperViewData<Pair<ED, CategoryInfo>, HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, ComposeViewData<HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, HConsK<ForComposeItemData, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HConsK<ForComposeItemData, Pair<CategoryInfo, SealedItemRenderer<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyRenderer<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingRenderer<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItemData>>>>>, MapperRenderer<Pair<ED, CategoryInfo>, HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, ComposeViewData<HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, HConsK<ForComposeItemData, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HConsK<ForComposeItemData, Pair<CategoryInfo, SealedItemRenderer<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyRenderer<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingRenderer<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItemData>>>>, ComposeRenderer<HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, HConsK<ForComposeItem, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HConsK<ForComposeItem, Pair<CategoryInfo, SealedItemRenderer<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyRenderer<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingRenderer<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItem>>>, HConsK<ForComposeItemData, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HConsK<ForComposeItemData, Pair<CategoryInfo, SealedItemRenderer<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyRenderer<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingRenderer<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItemData>>>>>>>, HConsK<Kind<ForSealedItem, Pair<ED, SearchConditions>>, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HNilK<Kind<ForSealedItem, Pair<ED, SearchConditions>>>>>>>, HConsK<ForComposeItemData, Pair<Pair<ED, SearchConditions>, MapperRenderer<Pair<ED, SearchConditions>, List<Pair<ED, NormalCategoryInfo>>, ListViewData<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>>, HNilK<ForComposeItemData>>>>
简化后只有1900多个字符:
ComposeRenderer<HConsK<ForIdT, Pair<ED, SearchConditions>, HConsK<ForIdT, Pair<ED, SearchConditions>, HNilK<ForIdT>>>, HConsK<ForComposeItemData, Pair<Pair<ED, SearchConditions>, SealedViewData<Pair<ED, SearchConditions>, HConsK<Kind<ForSealedItem, Pair<ED, SearchConditions>>, Pair<List<Pair<ED, CategoryInfo>>, ListViewData<List<Pair<ED, CategoryInfo>>, Pair<ED, CategoryInfo>, MapperViewData<Pair<ED, CategoryInfo>, HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, ComposeViewData<HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, HConsK<ForComposeItemData, Pair<List<Pair<ED, NormalCategoryInfo>>, ListViewData<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD>>, HConsK<ForComposeItemData, Pair<CategoryInfo, SealedViewData<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyViewData<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingViewData<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItemData>>>>>>>, HConsK<Kind<ForSealedItem, Pair<ED, SearchConditions>>, Pair<List<Pair<ED, NormalCategoryInfo>>, ListViewData<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD>>, HNilK<Kind<ForSealedItem, Pair<ED, SearchConditions>>>>>>>, HConsK<ForComposeItemData, Pair<Pair<ED, SearchConditions>, MapperViewData<Pair<ED, SearchConditions>, List<Pair<ED, NormalCategoryInfo>>, ListViewData<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD>>>, HNilK<ForComposeItemData>>>>
递归类型
函数式列表
函数范式中有List类型,但与OOP中的列表的结构是完全不同的:
List a = Nil | Cons a (List a)
或者用kotlin来描述为:
sealed class List<A>
object Nil : List<Nothing>
data class Cons<T>(val head: T, val tail: List<T>) : List<T>
它可以看做是一个自包含的数据结构:如果没有数据就是Nil;如果有数据则包含一个数据以及下一个List<T>
,直到取到Nil结束
即递归
数据结构
异构列表
强类型化最困难的在于ComposeRenderer的强类型化,ComposeRenderer可以实现的是组合不同的Renderer:
|-- item1 (eg: itemRenderer)
|-- item2 (eg: stringRenderer)
val composeRenderer = ComposeRenderer.startBuild
.add(itemRenderer)
.add(stringRenderer)
.build()
原始的实现方法可以通过一个List来保存:
List<BaseRenderer>
但这样就丢失了每个元素的类型特征信息,比如我们无法知道第二个元素是stringRenderer还是itemRenderer,这也是现有所有库都存在的问题,常常我们只能使用强制类型转换的方式来得到我们希望的类型,但没有类型系统的检查这种转换是不安全的,只能在运行期被检查出来
无论是OOP的列表还是上面介绍的函数式列表都无法满足这个需求,他们在add()
的时候都把类型特征丢弃了
而异构列表
可以将这些保留下来:
sealed class HList
object HNil : HList()
data class HCons<out H, out T : HList>(val head: H, val tail: T) : HList()
可以看到它的数据结构和原始的函数式列表的结构很相似,都是递归数据结构
但它在泛型中增加了out T : HList
,这是一个引用了自己类型的泛型声明,即:
HList = HCons<T1, HList>
HList = HCons<T1, HCons<T2, HList>>
HList = HCons<T1, HCons<T2, HCons<T3, HList>>>
HList = HCons<T1, HCons<T2, HCons<T3, HCons<T4, HList>>>>
...
它的类型可以在不断的代换中形成类型列表,这种即是递归类型
使用上:
// 原函数
fun test2(s: String, i: Int): List<Any?> = listOf(s, i)
// 异构列表
fun test2(s: String, i: Int): HCons<Int, HCons<String, HNil>> =
HCons(i, HCons(s, HNil))
同样是构建列表, 异构列表包含了更丰富的类型信息:
- 容器的size为2
- 容器中第一个元素为
String
, 第二个为Int
相比传统列表,异构列表的优势:
- 完整保存所有元素的类型信息
- 自带容器的size信息
- 完整保存每个元素的位置信息
这是基本的异构列表,DslAdapter为了做类型限定而自定义了高阶异构列表
,可以参考源码HListK.kt
递归类型
递归类型是指的包含有自己的类型声明:
fun <DL : HListK<ForIdT, DL>> test() = ...
这种泛型可以在不断代换中形成上面描述的类型列表:
HList = HCons<T1, HList>
HList = HCons<T1, HCons<T2, HList>>
HList = HCons<T1, HCons<T2, HCons<T3, HList>>>
HList = HCons<T1, HCons<T2, HCons<T3, HCons<T4, HList>>>>
...
在描述数量不确定的类型时很有用
辅助类型
在使用DslAdapter中可能会注意到有时会有一个奇特的参数type
:
fun <T, D, VD : ViewData<D>>
BaseRenderer<D, VD>.mapT(type: TypeCheck<T>,
mapper: (T) -> D,
demapper: (oldData: T, newMapData: D) -> T)
: MapperRenderer<T, D, VD> = ...
这个参数的实际值并不会在函数中被使用到,而跳转到TypeCheck
的定义中:
class TypeCheck<T>
private val typeFake = TypeCheck<Nothing>()
@Suppress("UNCHECKED_CAST")
fun <T> type(): TypeCheck<T> = typeFake as TypeCheck<T>
TypeCheck
只是一个没有任何功能的空类型,返回的值也是固定的同一个值,那这个参数究竟是用来干什么的?
要解释这个问题我们需要看一下mapT
这个函数的调用:
LayoutRenderer<String>(MOCK_LAYOUT_RES, 3)
.mapT(type = type<TestModel>(),
mapper = { it.msg },
demapper = { oldData, newMapData -> oldData.copy(msg = newMapData) })
上面这段代码的作用是将接收String
数据类型的Renderer转换为接受TestModel
数据类型
如果我们不使用type
这个参数的话这段代码会变成什么样呢:
LayoutRenderer<String>(MOCK_LAYOUT_RES, 3)
.map<TestModel, String, LayoutViewData<String>>(
mapper = { it.msg },
demapper = { oldData, newMapData -> oldData.copy(msg = newMapData) })
可以看到由于函数的第一个泛型T
类型系统也无法推测出来为TestModel
,因此需要我们显式地把T
的类型给写出来,但kotlin和Java的语法中无法只写泛型声明中的一项,所以我们不得不把其他可以被推测出来的泛型也显式的声明出来,不仅代码繁杂而且在类型复杂的时候几乎是不可完成的工作
参数type
中的泛型<T>
就可以用于辅助编译器进行类型推测,我们只需要手动声明编译器难以自动推测的部分泛型,而其他可以被推测的泛型还是交由编译器自动完成,即可简化代码
而这里的type = type<TestModel>()
即是辅助类型
,它的实例本身没有任何作用,它只是为了辅助编译器进行类型检查
最后
DslAdapter致力于完整的静态类型,使用了各种类型编程的技法,这是因为足够的类型信息不仅能帮编译器进行类型检查、早期排除问题,而且能够帮助我们在编码的时候编写足够准确
的代码
准确
的代码意味着我们即不需要处理不会发生的额外情况、也不会少处理了可能的情况。(早期版本的DslAdapter的更新模块就是被设计为自动
检查更新,导致需要处理大量额外情况,极其不安全)
同时DslAdapter本身不是一个希望做到全能的库,它的目标是将Adapter的工作足够简化,并只专注于Adapter工作,其他功能就交给专注其他功能的库
Do One Thing and Do It Well.
但这并不意味着DslAdapter是一个抛弃了功能性的库,相反,它是一个极其灵活的库。它的核心被设计得非常简单,只有BaseRenderer
和RendererAdapter
, 这两个类也相当简单,并且由于全局只有一个变量被保存在RendererAdapter中,保证了数据的线程安全性。而数据中都是不可变属性,使内部数据也可以被完全暴露出来,方便了功能的扩展
实际上DSL更新器 dsladapter-updater模块
以及DSL position获取器 dsladapter-position模块
都是以扩展方式存在的,后续还会根据需求继续扩展其他模块
本文只是浅尝则止地介绍了一点DslAdapter的开发技巧,欢迎Star和提出issue
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。