DslAdapter开发简介

DslAdapter是一个Android RecyclerView的Adapter构建器, DSL语法, 面向组合子设计. 专注类型安全, 所有代码采用Kotlin编写.

为什么要开发DslAdapter

实际上在DslAdapter开始开发的时点已经有很多RecyclerAdapter的扩展库存在了, 有的甚至从2015年开始已经持续开发到了现在. 从功能上来说, 这些库的各项功能都非常成熟了, 几乎所有的需求都有涉及; 而从思想上来说, 各种构建方式都有相应的库

以现在很常见的库举例:

  1. CymChad/BaseRecyclerViewAdapterHelper 1w6 star
    这是Github上检索出Star最多的RecyclerAdapter的库, 它支持添加Item事件添加列表加载动画添加头部、尾部树形列表等等,甚至设置空布局
  2. FastAdapter
    这个库是以前项目中也使用过的库, 功能也相当丰富, 相比上面的库更注重List的功能, 是一个从2015年开始开发一直持续维护的库
  3. KidAdapter
    kotlin编写,DSL语法的构建器,利用了kotlin的语法特性,构建上更简单,同时也实现了types功能。但功能上相比上面的库就简单很多了

甚至我多年前也已经写过一个相关库AdapterRenderer,也实现了很多功能。可以说RecyclerAdapter领域是最难以有新突破的地方了,似乎能做的只是在现有基础上小修小改而已了。

但现有的库真的已经完美到这种程度了吗?

不,现有的库也有很多的缺点:

  1. 非强类型,为了兼容多类型而直接忽略数据类型信息,失去了编译期类型检查,只能靠编写时自我约束
  2. 繁琐的模板代码。有些库会需要继承基础Adapter或者基础Holder继承方法来实现功能,因此会写大量的XXAdapter、XXItem
  3. 逻辑代码被迫分离。也是由于需要单独写XXAdapter、XXItem,而这些小类抽象度低,该界面的逻辑被迫分离到了这些小类中,即使是业务联系很大的逻辑
  4. 功能过于繁杂,抽象度低。功能上虽然丰富,但实际上包括了很多并不是RecyclerView应该关注的功能,导致变成了一个全功能的Adapter,Adapter的逻辑结构极其复杂,难以维护也难以扩展
  5. 类中变量和算法混杂,需要时常注意状态的同步问题

正因如此,为了解决以上这些问题,让我们编写的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呢:

  1. EmptyRenderer: 空Renderer, count为0
  2. LayoutRenderer: 与View绑定的末端Renderer, 可自定义数量
  3. ConstantItemRenderer: 将常量绑定到View的末端Renderer, 可适配任意数据源, 可自定义数量
  4. MapperRenderer: 转换目标Renderer的数据源类型, 一般通过mapT()来使用它
  5. ListRenderer: 将目标Renderer转换为适配列表数据源
  6. SealedItemRenderer: 根据数据源具体数据选择不同的Renderer渲染, 比如对于Int?类型,可以在为null的时候用EmptyRenderer渲染; 不为null的时候使用LayoutRenderer渲染
  7. ComposeRenderer: 组合多个不同Renderer
  8. 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>>

包含了TVDBR三个类型信息,但根据我们上面的分析,VDBR两个类型实际是等价的,他们包含了相同的类型特征,因此我们可以将其简化为<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))

同样是构建列表, 异构列表包含了更丰富的类型信息:

  1. 容器的size为2
  2. 容器中第一个元素为String, 第二个为Int

相比传统列表,异构列表的优势:

  1. 完整保存所有元素的类型信息
  2. 自带容器的size信息
  3. 完整保存每个元素的位置信息
这是基本的异构列表,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是一个抛弃了功能性的库,相反,它是一个极其灵活的库。它的核心被设计得非常简单,只有BaseRendererRendererAdapter, 这两个类也相当简单,并且由于全局只有一个变量被保存在RendererAdapter中,保证了数据的线程安全性。而数据中都是不可变属性,使内部数据也可以被完全暴露出来,方便了功能的扩展

实际上DSL更新器 dsladapter-updater模块以及DSL position获取器 dsladapter-position模块都是以扩展方式存在的,后续还会根据需求继续扩展其他模块


本文只是浅尝则止地介绍了一点DslAdapter的开发技巧,欢迎Star和提出issue


Yumenokanata
30 声望157 粉丝

Haskell中毒极深的Android开发工程师