Android Presenter与View的解耦讨论

作者 : 开心源码 本文共3712个字,预计阅读时间需要10分钟 发布时间: 2022-05-12 共144人阅读

这篇文章的主要内容前面其实我已经写过一遍了,但是我认为那篇形容的不是很清楚,因而又整理了一下。

本文主要探讨如何将Android中的Presenter以一种简洁的方式做到与View的解耦,并且不容易脱轨(变的混乱)。本文假设页面数据完全由Presenter管理。

我们先来看一下常规的PresenterView的写法(下文对于PresenterView的叙述简称为VP),并讨论一下这种写法存在什么问题:

常规的写法

对于Android中的VP,我们为了做到互相解耦,我们通常要给Presenter定义一个接口,给View定义一个接口, 假设我们要写一个搜索逻辑,可能会写出如下代码:

  1. 定义接口
     class SearchProtocol{        interface Presenter{            fun search()  //搜索        }            interface View {            fun showSearchResult() //显示搜索结果        }    }
  1. 接口实现
    class SearchPresenter : SearchProtocol.Presenter{ }    class SearchView : SearchProtocol.View{        val presenter:SearchProtocol.Presenter = LoginPresenter()        fun doSearch(){            presenter.search()        }        overried showSearchResult(){}    }

我认为这样写是存在少量问题的:

问题一 : 接口过多

PV还没开始写,两个接口先定义下来了。(尽管做到了PV肯定意义上的解耦)

问题二 : View依赖于固定的Presenter接口

比方大家经常使用的一种构建UI的方式 : 一个RecyclerView构建所有UI,页面不同的部分使用不同的RecyclerViewItem来体现。

如果下图这个搜索结果页就是使用RecyclerView构建的:

RecyclerView构建UI.png

假如客户点击挑选按钮(其实本质还是搜索),那么就需要调用persenter.search()。但是挑选这个item实际上是使用RecyclerView的一个ItemView构建的,因而我可能就需要把presenter(SearchPresenter)的实例传到这个ItemView,ItemView在挑选时调用presenter.search()

这样做可能有少量不好的地方:

  1. View依赖了一个固定的Presenter接口,VP存在耦合,不利于复用。假如在其余的界面我想复用这个ItemView,那么传另一个界面的Presenter很显著是不合适的。

  2. 不利于View的单元测试。其实RecyclerView中的ItemView也是一个View,假如在实例化这个View的时候还需要传一个指定的Presenter(SearchPresenter),那么单元测试这个View时为了提供它的环境就有点麻烦了,由于还要关心Presenter实例。

  3. 对于数据状态的获取Presenter也需要提供给View一个方法。

那怎样写可以处理上面的问题呢?我认为下面是一种可行的方案:

更纯净的VP写法

对于VP, 我认为他们之间的交流可以分为两种:

  1. View接收客户事件,触发Presenter执行少量逻辑,比方数据加载。
  2. View需要获取当前的数据状态,来决定UI的展示或者者UI层的少量逻辑,比方事件打点。

形容上面两种交流方式,可以把Presenter笼统为下面这个接口:

    open class Action()     open class State()    abstract class BasePresenter()  {         abstract fun dispatch(action:Action)        abstract fun <T : State> queryStatus(statusClass: KClass<T>): T?    }

Action : View触发的操作,可以通过一个Action来通知Presenter

State : 形容View可以从Presenter中取得的数据的状态。

BasePresenter : View只依赖这个最笼统的接口。通过ActionState来与Presenter交互。

下面详细来解释一下ActionState的思想:

使用Action统一Presenter的解决逻辑

在往下阅读之前可以先看一下这篇文章 : https://segmentfault.com/a/1190000008736866
这篇文章详情了redux的设计思想,而下文所要详情的Presenter的实现就是借鉴了Redux的设计思想。

对于常规的写法,Presenter的解决逻辑是通过调用固定的方法实现的,这就导致依赖于一个固定的Presenter接口, 参考Redux的设计,可以这样设计Presenter:

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

即所有的Presenter都实现这一个接口,外界对于Presenter逻辑的触发都通过dispatch()方法实现,对于上面搜索那个例子可以这样实现:

    class SearchAction(val keyword:String) : Action    class SearchPresenter(searchView:SearchViewProtocol):BasePresenter{        overried fun dispatch(action:Action){            when(action){ //只解决感兴趣的action                is SearchAction -> doSearch()            }        }        fun doSearch(){          //...          searchView.showSearchResult()        }    }    class SearchView:SearchViewProtocol{        val presenter:BasePresenter = SearchPresenter(this)         fun doSearch(){            presenter.dispatch(SearchAction("narato"))        }        ......    }

这样写后比照于常规的写法有什么好处呢?

  1. 减少了Presneter接口的定义,因为现在Presenter对外层的笼统是dispatch方法,因而新的VP不需要特别定义与View配套的Presenter接口。
  2. View不依赖于固定的Presenter接口,统一使用BasePresenter,View可以很好的复用和进行单元测试。
  3. View发出的ActionPresenter可以选择解决,也可以不解决。

View使用State来获取当前的状态

在Redux中,View dispatch Action后对于数据的变化,可以通过订阅(观察)数据来刷新UI。不过对于这次我详情的VPView的数据是由Presenter所提供的,那么就不能使用Redux这种方法了(View不会直接接触数据)。

举一个例子,比方有一个自己设置按钮,它能否可以点击执行少量事情,依赖于当前界面某些数据的状态。这个状态并不属于当前View

那常规我们可能会这样做:

    //View中的按钮被点击    class MyBtton(presenter:SearchPresenter){        fun onClick(){            if(presenter.canExecute()){            }        }    }

假如这样写那就又会出现上面的问题:

  1. 依赖具体的presenter,复用困难
  2. 单元测试麻烦
  3. 为获取状态,又多了一个方法

我们可以借用dispatch的设计,引入State:

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

即我们可以这样实现这个需求:

    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            }        }    }

这样的做法不仅处理了上面的问题。并且SearchState是一个对象,我们可以封装许多数据的状态,减少State的定义。

上面只是我应用在目前业务中的一种PV写法,当然对于不同的业务,可能这套写法会出现问题,欢迎探讨。

欢迎关注我的Android进阶计划看更多干货

欢迎关注我的微信公众号:susion随心

微信公众号.jpeg

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Android Presenter与View的解耦讨论

发表回复