再读斋

Android开发模式之MVP

背景

在开发Android App时,越到最后肯定会发现,Activity的负担非常重,既要初始化控件,又要写一些逻辑操作的展示等等,很多Activity中的代码都充当了Controller和Model的角色,因而发现Activity违背了单一职责原则,负担过重。所以,MVP架构模式应运而生。

MVP架构是什么

MVP就是Model-View-Presenter,MVP是从经典的MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。作为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不会直接使用Model,它们之间的通信是通过Presenter(MVC中是Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过Controller。

在MVC里,View是可以直接访问Model的,从而View里会包含Model信息,不可避免的还要包括一些业务逻辑。在MVC模型里,更关注的是Model的不变,而同时有多个对Model的不同显示的View。所有在MVC模型里,Model不依赖于View,但是View依赖于Model的。不仅如此,因为有一些业务逻辑在View里实现了,导致要更改View也是比较困难的,至少那些业务逻辑是无法重用的。

用流程图的方式解释就更清楚了:

MVP流程图

MVC与MVP的区别

MVP架构

View:对应于Activity,负责View的绘制以及与用户交互、
Model:业务逻辑和实体模型
Presenter:负责完成View与Model间的交互

MVP示意图

  • View不直接与Model交互,而是通过Presenter交互来与Model间接交互
  • Presenter与View的交互是通过接口来进行的
  • 通过View与Presenter是一对一的,但复杂的View可能绑定多个Presenter来处理逻辑

MVC架构

View:对应于布局文件
Model:业务逻辑和实体模型
Controller:对应于Activity

mvc示意图

  • View可以和Model直接交互
  • Controller是基于行为的,并且可以被多个View共享。
  • 可以复杂决定显示哪个View

总的就是说:从MVC到MVP的一个转变,就是减少了Activity的职责,减轻了它的负担,简化了Activity中的代码和一些操作,将逻辑代码提取到了Presenter中进行处理,降低其耦合度。

在MVP里,Presenter完全把Model和View进行了分离,主要的程序逻辑在Presenter里实现。而且,Presenter与具体的View是没有直接关联的,而是通过定义好的接口进行交互,从而使得在变更View时候可以保持Presenter的不变,即重用! 不仅如此,我们还可以编写测试用的View,模拟用户的各种操作,从而实现对Presenter的测试–而不需要使用自动化的测试工具。 我们甚至可以在Model和View都没有完成时候,就可以通过编写Mock Object(即实现了Model和View的接口,但没有具体的内容的)来测试Presenter的逻辑。 在MVP里,应用程序的逻辑主要在Presenter来实现,其中的View是很薄的一层。因此就有人提出了Presenter First的设计模式,就是根据User Story来首先设计和开发Presenter。在这个过程中,View是很简单的,能够把信息显示清楚就可以了。在后面,根据需要再随便更改View,而对Presenter没有任何的影响了。 如果要实现的UI比较复杂,而且相关的显示逻辑还跟Model有关系,就可以在View和Presenter之间放置一个Adapter。由这个 Adapter来访问Model和View,避免两者之间的关联。而同时,因为Adapter实现了View的接口,从而可以保证与Presenter之间接口的不变。这样就可以保证View和Presenter之间接口的简洁,又不失去UI的灵活性。 在MVP模式里,View只应该有简单的Set/Get的方法,用户输入和设置界面显示的内容,除此就不应该有更多的内容,绝不容许直接访问Model–这就是与MVC很大的不同之处。

MVP的优点

  • 降低耦合度,隐藏数据,Activity中代码更简洁
  • 模块职责划分明显
  • 方便测试驱动开发
  • 代码复用度较高
  • 代码灵活性增强

MVP架构模式示例

这个示例是根据用户id获取用户信息并展示的一个过程,其中获取信息用了一个线程进行了模拟获取。

先看一下MVP包结构图:

1.Model层

首先是建一个JavaBean User实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.deason.mvpdemo.bean;
public class User {
private String name;
private String id;
private String sex;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}

新建Model层抽象接口

1
2
3
4
public interface IGetUser {
public void getUserInfo(int id,OnUserInfoListener listener);
}
1
2
3
4
5
public interface OnUserInfoListener {
public void getUserInfoSuccess(User user);
public void getUserInfoFailed();
}

Model层抽象接口实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class GetUserInfo implements IGetUser{
@Override
public void getUserInfo(final int id, final OnUserInfoListener listener) {
// TODO Auto-generated method stub
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
SystemClock.sleep(5000); //模拟子线程耗时操作
if (id == 1) {
User user = new User();
user.setName("liu");
user.setAge("27");
user.setSex("男");
user.setId("1");
listener.getUserInfoSuccess(user);
} else {
listener.getUserInfoFailed();
}
}
}).start();
}
}

2.View层

Presenter和View交互是通过接口,所有我们需要定义一个IShowUserView的接口,这个接口封装的方法基本都跟视图展示有关。

1
2
3
4
5
6
7
public interface IShowUserView {
public void showLoading();
public void hideLoading();
public void toMainActivity(User user);
public void showFailedError();
}

3.Presenter层

Presenter是Model和View之间交互的桥梁,里面有一些业务逻辑的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class UserInfoPresenter {
private IGetUser iGetUser;
private IShowUserView iShowUserView;
private Handler mHandler = new Handler();
public UserInfoPresenter(IShowUserView iShowUserView) {
this.iShowUserView = iShowUserView;
this.iGetUser = new GetUserInfo();
}
public void getUserInfoToShow(int id) {
iShowUserView.showLoading();
iGetUser.getUserInfo(id, new OnUserInfoListener() {
@Override
public void getUserInfoSuccess(final User user) {
// UI线程执行
mHandler.post(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
iShowUserView.toMainActivity(user);
iShowUserView.hideLoading();
}
});
}
@Override
public void getUserInfoFailed() {
// TODO Auto-generated method stub
mHandler.post(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
iShowUserView.showFailedError();
iShowUserView.hideLoading();
}
});
}
});
}
}

4.Activity中调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class MainActivity extends Activity implements IShowUserView {
private TextView mNameTxt;
private TextView mAgeTxt;
private TextView mSexTxt;
private Button mLoadBtn;
private ProgressDialog mDialog;
private UserInfoPresenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPresenter = new UserInfoPresenter(this);
mDialog = new ProgressDialog(this);
mNameTxt = (TextView) findViewById(R.id.tv_name);
mAgeTxt = (TextView) findViewById(R.id.tv_age);
mSexTxt = (TextView) findViewById(R.id.tv_sex);
mLoadBtn = (Button) findViewById(R.id.btn_load);
mLoadBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
mPresenter.getUserInfoToShow(1);
}
});
}
@Override
public void showLoading() {
// TODO Auto-generated method stub
mDialog.show();
}
@Override
public void hideLoading() {
// TODO Auto-generated method stub
mDialog.dismiss();
}
@Override
public void toMainActivity(User user) {
// TODO Auto-generated method stub
mNameTxt.setText(user.getName());
mAgeTxt.setText(user.getAge());
mSexTxt.setText(user.getSex());
}
@Override
public void showFailedError() {
// TODO Auto-generated method stub
Toast.makeText(this, "Load failed", Toast.LENGTH_SHORT).show();
}
}

可以看出,虽说是代码量增加了,但是Activity中的代码变得简洁起来,程序也清晰明了,好处还是很多的。好记性不如烂笔头,勤加练习和实践。

刘涤生 wechat