前言
关于MVP模式系列的文章,前面已经写了3篇了,本来想等以后对MVP模式有了更深层次的理解后再来总结一下的,但是最近在研究Adapter和Activity或Fragment解耦的时候,突然想到了View和Presenter之间的解耦,索性就尝试了一下,然后来和大家分享一下。
郑重声明:
由于我对架构的经验不足,所以在这里只在MVP框架体系下讨论Presenter和View之间的关系解耦(也可能只能称得上是弱化了他们之间的联系),如果修改到了框架本身的属性,请大家忽略,或者大家提供一个在不改变MVP模式本身的前提下解耦Presenter和View的方式,感激不尽。
一、原理简介
我们先看一下基本的MVP的原理图
在这种模式下,Presenter不可避免的需要持有View的引用,同时View也不可避免的需要持有Presenter,当然,这是MVP模式本身的一个特性,一个Model和一个Presenter还有一个View构成了MVP模式的最小单元。但是在工作中的业务不可能总是能局限于某种模式,所以我们需要变化,需要更适合业务的模式。
假如我们思考这样一个问题,我们有一个鱼塘用来养鱼,Presenter里面有个叫(捕鱼)fishing的方法提供给View,后来由于市场原因,鱼不好卖了,我们开始在鱼塘里养虾,那么,捕鱼这个方法要改名字,要改成(捕虾)shrimp。大家一想,这个很简单啊,用编辑器的ReName一下就搞定了。但是这个改动的地方可不只是Presenter,还有View,因为View以前是调用Presenter的fishing方法,现在要改成调用Presenter的shrimp方法了,代码肯定会发生变化。
那么有没有一种办法,让View和Presenter互相不关注他们之间联系的方法的名字,而只关心对方提供的功能呢?答案是肯定的,那就是一定有这样的方法,并且不止一种,那么我们今天要谈的就是其中的一种。先看一下原理图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cV9GeB5m-1572250453736)(https://ykbjson.github.io/blogimage/mvppicture4/mmvp.png)]
我们不妨让一个中间件来集中管理View和Presenter的关系,在View层用一个注解告诉中间件我要绑定哪个或哪几个Presenter,View和Presenter提供的每一个业务方法都用一个唯一标识来表示,Presenter和View之间某个方法的调用就是通过中间件发送一个包含某个方法持有的特定标识的Action来找到某个方法,而方法的调用是基于注解提供的标识,所以可以不关心彼此提供的方法的名称。这似乎很难以理解,也很难想象,那么,我就换种方式阐述一下。
我们把这个中间件看成一个Presenter和View的功能映射关系提供者即可,这个这个中间件提供一个注册View的方法,然后根据View的需求(即View上的注解)去生成对应的Presneter,并把他们的关系存储起来,然后提供一个可以发送一个特定Action的方法,这个Action里面有一个发送者的class、接收者class还有一个code和一些参数,这个code应着接收者里某个方法注解上的code,当中间件通过刚才存储的关系,找到发送者的class和接收者class的关系后,然后根据Action里面的code决定执行接收者的哪个方法。这所有的逻辑都在中间件里面完成,无需外部实现,View和Presenter的关系就变得更模糊了。
如果说文字阐述不清楚这个原理,那么就只能上代码了,代码是最好的老师…(真的吗???)
二、代码实现
库代码结构图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uIRnD5UU-1572250453736)(https://ykbjson.github.io/blogimage/mvppicture4/mmvpjiegou.png)]
接下来,我们一个一个的来为大家解释每一个类的定义与作用,我们先从那两个注解开始吧。
BindPresenter,顾名思义,就是绑定Presneter
/**
* 包名:com.ykbjson.lib.mmvp.internal
* 描述:View绑定与Presenter的注解
* 创建者:yankebin
* 日期:2018/4/12
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindPresenter {
/**
* View需要绑定的Presenter数组
*/
Class<? extends MMVPPresenter>[] value();
}
这里为什么是一个数组呢?因为严格意义上来讲,MVP模式下,Presenter和View是一对一的关系,但是我在想,既然View和Presenter的关系已经变得模糊了,那么我是否可以把Presenter当成一种或一类功能的提供者,供不同的View绑定和调用。但是这样似乎又违背了MVP模式的定义,所以先暂且声明成一个数组吧。
ActionProcess,这个可能不太好理解,因为我也没有想好特别能解释它作用的名字
/**
* 包名:com.ykbjson.lib.mmvp.internal
* 描述:Presenter或View提供的可供反射调用的方法的注解
* 创建者:yankebin
* 日期:2018/4/12
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActionProcess {
/**
* action
**/
String value() default "";
/**
* 是否需要{@link com.ykbjson.lib.mmvp.MMVPAction}参数
**/
boolean needActionParam() default false;
/**
* 是否需要转换{@link com.ykbjson.lib.mmvp.MMVPAction},即交换sourceClass和targetClass
**/
boolean needTransformAction() default false;
/**
* 是否需要{@link com.ykbjson.lib.mmvp.MMVPAction}里面的param参数
**/
boolean needActionParams() default false;
}
其实这就是刚才我所说的“Presenter和View之间某个方法的调用就是通过中间件发送一个包含某个方法持有的特定标识的Action来找到某个方法,而方法的调用是基于注解提供的标识”,那个注解就是ActionProcess,里面的value对应我所说的code(在代码设计的时候我还是命名成了action),其他的都有详细的注释,后面Demo里也会用到,这里我就不在赘述了。总之,这个注解就是注册于方法之上,告诉中间件,我注册了某个action,需要些什么参数,然后你就可以调用我了。
既然都说到了Action,那我们就继续看看他的内部实现。
MMVPAction,很明显,这是一个动作或者叫做行为
/**
* 包名:com.ykbjson.lib.mmvp
* 描述:View和Presenter之间的通信携带
* 创建者:yankebin
* 日期:2018/4/12
*/
public class MMVPAction implements Serializable, Cloneable {
private Class<?> sourceClass;
private Class<?> targetClass;
private MMVPActionDescription action;
private Map<String, Object> params;
private IMMVPOnDataCallback onDataCallback;
MMVPAction(Class<?> sourceClass, Class<?> targetClass) {
this.sourceClass = sourceClass;
this.targetClass = targetClass;
}
public MMVPAction setSourceClass(Class<?> sourceClass) {
this.sourceClass = sourceClass;
return this;
}
public MMVPAction setTargetClass(Class<?> targetClass) {
this.targetClass = targetClass;
return this;