Android中的P與V如何寫更解耦

本文主要討論如何將Android中的 Presenter 以一種簡潔的方式做到與View的解耦,即View只依賴于最抽象的Presenter接口, 而不是具體的Presenter接口。

常規(guī)的寫法

對于Android中的VP我們?yōu)榱俗龅交ハ嘟怦?,我們通常要給Presenter定義一個接口,給View定義一個接口, 假設(shè)我們要寫一個搜索邏輯,可能會寫出如下代碼:

  1. 定義接口
     class SearchProtocol{
        interface Presenter{
            fun search() //搜索
        }    

        interface View {
            fun showSearchResult() //顯示搜索結(jié)果
        }
    }
  1. 接口實(shí)現(xiàn)
    class SearchPresenter : SearchProtocol.Presenter{ }

    class SearchView : SearchProtocol.View{

        val presenter:SearchProtocol.Presenter = LoginPresenter()

        fun doSearch(){
            presenter.search()
        }

        overried showSearchResult(){}
    }

這樣寫有什么問題呢 ?

  • VP還沒開始寫,兩個接口先定義下來了

  • 對于某些例子, 會導(dǎo)致View依賴于Presenter

比如說現(xiàn)在大家經(jīng)常使用的一種構(gòu)建UI的方式:一個RecyclerView構(gòu)建所有UI,假如下圖這個搜索結(jié)果頁就是使用RecyclerView構(gòu)建的:

如果用戶點(diǎn)擊篩選按鈕(其實(shí)本質(zhì)還是搜索),那么就需要調(diào)用 persenter.search()。但是篩選這個item實(shí)際上是使用RecyclerView的一個Item構(gòu)建的,因此我可能就需要把presenter傳到這個ItemView,ItemView在篩選時調(diào)用presenter.search()

這樣做有什么不好呢?:

  1. 依賴了一個固定的presenter接口,不利于復(fù)用,如果在其他的界面我想復(fù)用這個ItemView,那么傳另一個界面的Presenter很明顯是不合適的。

  2. 不利于單元測試: 其實(shí)RecyclerView中的ItemView也是一個View,如果在實(shí)例化這個View的時候還需要傳一個指定的Presenter,那么單元測試這個View時為了提供它的環(huán)境就有點(diǎn)麻煩了。

更純凈的VP寫法

統(tǒng)一Presenter的處理邏輯

在往下閱讀之前可以先看一下這篇文章 : https://segmentfault.com/a/1190000008736866
這篇文章介紹了redux的設(shè)計思想,而下文所要介紹的Presenter的新實(shí)現(xiàn)就是借鑒了Redux的設(shè)計思想。

對于常規(guī)的寫法,Presenter的處理邏輯是通過調(diào)用固定的方法實(shí)現(xiàn)的,這就導(dǎo)致依賴于一個固定的Presenter接口, 參考Redux的設(shè)計,我們可以這樣設(shè)計Presenter:

    class Action

    class BasePresenter{
        abstract fun dispatch(action: Action)
    }

即所有的Presenter都實(shí)現(xiàn)這一個接口,外界對于Presenter邏輯的觸發(fā)都通過dispatch()方法實(shí)現(xiàn),對于上面搜索那個例子可以這樣實(shí)現(xiàn):

    class SearchAction(val keyword:String):Action

    class SearchPresenter(searchView:SearchViewProtocol):BasePresenter{
        overried fun dispatch(action:Action){
            when(action){
                is SearchAction -> doSearch()
            }
        }

        fun doSearch(){
          //...
          searchView.showSearchResult()
        }
    }

    class SearchView:SearchViewProtocol{

        val presenter:BasePresenter = SearchPresenter(this)

         fun doSearch(){
            presenter.dispatch(SearchAction("narato"))
        }
        ......
    }

這樣寫后對比于常規(guī)的寫法有什么好處呢?

  1. 減少了Presneter接口的定義,由于現(xiàn)在Presenter對外層的抽象是dispatch方法,因此新的VP不需要特定定義與View配套的Presenter接口。
  2. View不依賴于固定的Presenter接口,統(tǒng)一使用BasePresenter,View可以很好的復(fù)用和進(jìn)行單元測試。
  3. View發(fā)出的Action,Presenter可以選擇處理,也可以不處理。

View對于狀態(tài)的獲取

在Redux中,View dispatch Action后對于數(shù)據(jù)的變化,可以通過訂閱(觀察)數(shù)據(jù)來刷新UI。不過對于這次我介紹的VP,View的數(shù)據(jù)是由Presenter所提供的,那么就不能使用Redux這種方法了。
其實(shí)在Android中,對于VP,我們 認(rèn)為且應(yīng)該 :View所需要的數(shù)據(jù)應(yīng)該在presenter刷新UI時由Presenter傳遞過來, 比如:

presenter.showSearchResult(result)

即,View只負(fù)責(zé)展示UI,不應(yīng)有其他邏輯。上面這種方式在一定程度上可以使View完成自己的職責(zé),但在一些情況下就有問題了:

比如有一個按鈕,它是否可以點(diǎn)擊執(zhí)行一些事情,依賴于當(dāng)前界面某些數(shù)據(jù)的狀態(tài)。

那常規(guī)我們可能會這樣做:

class MyBtton(presenter:Presenter){
    fun onClick(){
        if(presenter.canExecute()){

        }
    }
}

如果這樣寫那就又會出現(xiàn)上面的問題:

  1. 依賴具體的presenter,復(fù)用困難
  2. 單元測試麻煩

為了達(dá)到 view完全依賴抽象的Presenter 我們可以借用dispatch的設(shè)計:

    class SeachState

    class SeachBasePresenter{
        fun <T : SeachState> queryState(statteClass: KClass<T>): T?
    }

即我們可以這樣實(shí)現(xiàn)這個需求:

    class MyBtton(presenter:SeachBasePresenter){
        fun onClick(){
            if(presenter.queryState(MyButtonState::class)?.canExecute == true){

            }
        }
    }

    class MyButtonState(val canExecute:Boolean = false):SearchState

    class SeachButtonPresenter{
        override fun <T : SearchState> queryStatus(statusClass: KClass<T>): T? {
            return when (statusClass) {
                MyButtonState::class -> {
                    MyButtonState(true) as T
                }
                else -> null
            }
        }
    }

這樣做依舊是達(dá)到了View只依賴于抽象的SearchBasePresenter的目的,不依賴于具體的Presenter,解決了上面的問題。

總結(jié)

因此我們在設(shè)計VP結(jié)構(gòu)時可以設(shè)計成這種結(jié)構(gòu),可以達(dá)到View完全依賴于抽象的Presenter,保證程序在正確的軌道上發(fā)展:

    open class Action()

    open class State()

    abstract class BasePresenter()  {

        abstract fun dispatch(action:Action)

        abstract fun <T : State> queryStatus(statusClass: KClass<T>): T?
    }

歡迎關(guān)注我的Android進(jìn)階計劃

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容