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

相關文章
相關標籤/搜索