DslAdapter开发简介

DslAdapter开发简介

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

为何要开发DslAdapter

实际上在DslAdapter开始开发的时点已经有不少RecyclerAdapter的扩展库存在了, 有的甚至从2015年开始已经持续开发到了如今. 从功能上来讲, 这些库的各项功能都很是成熟了, 几乎全部的需求都有涉及; 而从思想上来讲, 各类构建方式都有相应的库 github

以如今很常见的库举例:算法

  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更简单、更安全,从而有了开发一个新库的想法app

一个新的Adapter库应该是什么样的

想要构建一个Adapter的库,首先咱们要想一想咱们这个新库应该是干什么的,这就须要回到RecyclerView这个库中Adapter被定义为何。ide

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

相关文章
相关标签/搜索