关于Android MVP模式的思考

最近经常看到各种介绍MVP模式的博客的,之前写过不少的Android应用,在做那些应用的时候,都是要求快速完成,所以从开始设计到写代码就一直考虑着重用。以前写的项目基本都是不断重构项目,将项目代码变得更加精简,提高代码之间的复用性。但是代码并没有特别地注重按照MVC模式或者是MVP模式来,更多的是直接考虑模块化,重用,精简。所以看了MVP模式后,决定去总结一下自己代码中的问题并优化,算是对自己之前写的代码的回顾。

MVP框架

MVP框架是目前在Android流行起来的框架,它非常适合用于Android开发上面。我最早接触MVP模式是在一本敏捷开发的书上。MVP分别指代M(model),V(View),P(Presenter):

  • M(Model):表示数据模型,以及相关的数据结构。
  • V(View):表示视图,主要是指UI界面相关的那些东西。在Android里面比如说layout的xml文件,在MVP中,很多时候Activity/Fragment也是被看做View。
  • P(Presenter):可以直接理解为视图与模型的中间纽带。

此处输入图片的描述

在MVP模式中,Model和View不直接进行关联,而是通过Presenter来进行关联的。往往实现的方式是增加一个IModel接口和IView接口,用Model和View分别实现那两个接口,Presenter里面保存IModel和IView接口类型的成员变量。

代码示例

下面举一个关于Android用户信息的MVP模式例子。

1
2
3
public interface IUserView{
void setName(String name);
}

用户的JavaBean—User:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class User{
private String name;
private String id;
public String getName(){
return name;
}
public String getId(){
return id;
}
public void setName(String name){
this.name = name;
}
public void setId(String id){
this.id = id;
}
}

用户模型接口

1
2
3
4
public interface IUserModel{
void saveUser(User user);
User load();
}

真正模型

1
2
3
4
5
6
7
8
9
public class UserModel implements IUserModel{
void saveUser(User user){
// 保存用户信息,保存在本地数据库中,或者xml里面
}
User loadUser(){
// 从网络中,或者从本地缓存中读取用户信息
return null; //未实现
}
}

Presenter:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserPresenter{
private IUserView view;
private IUserModel userModel;
public UserPresenter(IUserView view){
this.view = view;
userModel = new UserModel();
}
public void loadUser(){
// 此处可能是异步load
User user = userModel.loadUser();
view.setName(user.getName());
}
}

把Activity作为View来看待

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class UserActivity extends Activity implements IUserView{
TextView tvName; //名称对应的TextView
UserPresenter userPresenter ;
public void onCreate(Bundle bundle){
...
userPresenter = new UserPresenter(this);
// userPresenter.loadUser(); 可以有先初始化
}
void setName(String name){
tvName.setText(name); //如果presenter的loadUser是异步线程,这里可以通过tvName.post来运行在UI线程。
}
void reloadUserData(){ //该函数可以某个按钮的onClick事件里面调用
userPresenter.loadUser(); // 可以是从网络中加载数据
}
}

上面代码中Activity不再与Model直接关联,而是通过Presenter来间接关联Model。并且当Model的数据变化了的时候,Presenter能够通知View。上面的例子是View需要变化了,请求Presenter获取数据。

分析

MVP与MVC最大的不同就是View和Model不再直接关联。很多Android的MVC模式都是直接将Activity看作Control,这会导致整个Activity非常臃肿,因为它既然进行UI交互,还需要加上Control这部分的功能。另外即使另外新建一个Control,而把Activity只当做View的功能,如果Activity还是直接跟Model直接关联的话,因为跟Model直接关联,还是会在Activity增加很多操作。而使用P作为Model和View的纽带,P可以先对模型数据进行一些处理,然后再显示到View。另外一方面对于View的一些请求,Presenter也可以进行一些处理再去请求Model。

上面是关于代码方面的优势,其实通过分隔开Model和View,也是将各个模块进行了解耦。另外一方面通过增加IUserView和IUserModel,这样每个部分进行单元测试也更加方便了,比如可以直接实现IUserView来模拟测试Presenter。

个人觉得MVP模式适合Android应用,一个很大的原因就是Activity这种组件的存在,UI交互完全放在了Activity里面,这导致很多时候Activity会在一不注意间就变得臃肿。View过于庞大。当然还有很多跟Android相关的优势,比如说能够更好地避免Activity的内存泄漏(Presenter直接引用View,而不是Activity的时候)。其实通过增加Presenter,一定程度上也增加了Presenter的复用,很多人说View和Presenter是一一对应的,但是我觉得如果不一一对应,比如说一个Activity里面包含多个Presenter,将Presenter细化,那样Presenter复用的可能性也就越高了,同时也避免了Presenter过于臃肿。

一个模式优化过程

平时自己写代码的时候,其实还真的是比较少那么明显地使用这种MVP模式,或者说是MVC模式。更多的是模块化,类层次化(面向对象),面向切面,并且坚持代码精简,复用的原则。下面介绍一个将平时的项目简化后的模式,并且通过考虑优化结构将模式转变成MVP的过程。先看看下面这种类型结构:
此处输入图片的描述

先模块化将程序分为Fragment/Activity部分,Adapter部分,模型网络操作部分,通过将相关类进行层次化来减少类的臃肿,另外通过AOP编程的方式将一些内容从子父类中提取出来,作为一个单独的切面。这种面向切面能够提高代码的复用性。

上面的类型图里面也有一个不合理的例子,正如上面的注释中所说的将网络操作作为模型的一部分,而不是再添加一个接口,会导致网络操作一旦修改,会影响到Model的子类。

下面看看如何对上面的结构进行转换。上面这种结构更多的是MVC的方式,而且是将V和C基本合在一起了,内聚在一起能够更方面UI交互,但是也会导致臃肿。上面的图中发现只有BaseFragment单向地去关联Model,这样势必会导致Fragment增加更多的代码来控制,因为在Android中进行网络连接是一定要在非UI线程操作的,如果Model没有关连Fragment,必然需要将一个异步操作放到Fragment,等待Model操作完成了后,Fragment来更新Fragment的内容。如果增加一个接口,然后Fragment实现,并且用Model引用它,那么将能够减少Fragment的操作,而且能够更好地去对Model进行单元测试。如果增加一个Presenter,由Presenter与Fragment相互关联,然后Presenter也与Model相互关联,那么Fragment将会大大简化,更加专注一些其他的UI交互。增加Presenter整个架构就变成了MVP模式了。重构后的类型图如下:

此处输入图片的描述

总结

模式总是坚持着复用,模块间低耦合,模块内高内聚等等原则来进行的,设计模式中就有六大原则: 单一职责原则,开闭原则,依赖倒转原则,迪米特法则,里氏替换原则,组合聚合原则。好的模式能够让人在阅读的时候能够很好地理解代码,在对程序进行修改的时候能够快速简洁,并且不对原有代码结构破坏。