浅析 Android 中的三大模式:MVC, MVP 和 MVVM (一)

文章目录
  1. 1. MVC 模式
  2. 2. MVP 模式

日拱一卒,功不唐捐。

又是一年教师节,首先,恭祝天下的教师们节日快乐。尤其,恭祝所有带过我的老师们,毕竟,没有你们的谆谆教诲,也没有我的今天。而今,虽然尚在魔都挣扎,但毕竟是从小地方一路走过来的,未来的路很长,且歌且行。好,进入这期的正题,简单分析下 Android 中的三大模式:MVC,MVP 和 MVVM。

这三大模式,很多码农应该听说过或者用过,招聘启事中也常会有要求“谈谈你对三大模式中一种或几种的理解”,时下一些紧跟技术发展方向的公司代码中也在运用。所以,理解乃至掌握这三大模式,是十分有必要的。下面,以实现简单的登录效果为实例,分别谈谈这三种模式,效果图如下:

MVC 模式

MVC,即 Model View Controller,软件开发中最常用的一种模式。一句话概括,即通过 Controller 层操作 Model 层的数据,再返回给 View 层展示结果,如下图所示:

用户分发事件,View 层发送指令到 Controller 层,然后,Controller 层通知 Model 层更新数据,最后,Model 层更新完数据,在 View 层显示结果。Demo 地址:MVCLoginDemo,部分代码如下:

首先,View 层的 XML 文件:

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
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:stretchColumns="1"
tools:context=".controller.MainActivity">


<TableRow>
<TextView
android:layout_height="wrap_content"
android:text="Username:"/>


<EditText
android:id="@+id/et_username"
android:layout_height="wrap_content"
android:ems="10"/>

</TableRow>

<TableRow>
<TextView
android:layout_height="wrap_content"
android:text="Password:"/>


<EditText
android:id="@+id/et_password"
android:layout_height="wrap_content"
android:ems="10"/>

</TableRow>

<Button
android:id="@+id/btn_login"
android:layout_height="wrap_content"
android:layout_span="2"
android:text="Login"/>


<Button
android:id="@+id/btn_clear"
android:layout_height="wrap_content"
android:layout_below="@id/btn_login"
android:layout_span="2"
android:text="Clear"/>


<ProgressBar
android:id="@+id/pb_load"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"/>

</TableLayout>

两个填充框,两个按钮和一个可以控制是否显示的进度圈。

其次,Controller 层的 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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
public class MainActivity extends Activity {

private EditText mEtUsername;
private EditText mEtPassword;
private Button mBtnLogin;
private Button mBtnClear;
private ProgressBar mPbLoad;
public IUserBiz mUserBiz;
private Handler mHandler = new Handler();

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
this.mUserBiz = new UserBiz(); // 不要遗漏, 否则报空指针异常
initView();
}

private void initView() {
mEtUsername = (EditText) findViewById(R.id.et_username);
mEtPassword = (EditText) findViewById(R.id.et_password);
mBtnClear = (Button) findViewById(R.id.btn_clear);
mBtnLogin = (Button) findViewById(R.id.btn_login);
mPbLoad = (ProgressBar) findViewById(R.id.pb_load);

mBtnLogin.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
login();
}
});

mBtnClear.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
clear();
}
});
}

public void login() {
showLoading();
mUserBiz.login(getUsername(), getPassword(), new OnLoginListener() {

@Override
public void loginSuccess(final User user) {
// 需要在 UI 线程中执行
mHandler.post(new Runnable() {

@Override
public void run() {
showSuccess(user);
hideLoading();
}
});
}

@Override
public void loginFailed() {
// 需要在 UI 线程中执行
mHandler.post(new Runnable() {

@Override
public void run() {
showFailedError();
hideLoading();
}
});
}
});
}

public void clear() {
clearUsername();
clearPassword();
}

public String getUsername() {
return mEtUsername.getText().toString();
}

public String getPassword() {
return mEtPassword.getText().toString();
}

public void clearUsername() {
mEtUsername.setText("");
}

public void clearPassword() {
mEtPassword.setText("");
}

public void showLoading() {
mPbLoad.setVisibility(View.VISIBLE);
}

public void hideLoading() {
mPbLoad.setVisibility(View.GONE);
}

public void showSuccess(User user) {
Toast.makeText(this, "Login Success!", Toast.LENGTH_SHORT).show();
}

public void showFailedError() {
Toast.makeText(this, "Login Failed!", Toast.LENGTH_SHORT).show();
}
}

登录效果的主要实现类,用户名和密码与初始设置的一样时,显示登录成功,否则,显示登录失败。Login 项,一般是连接服务器的,是个耗时的操作,故开辟了一个子线程,Thread.sleep(2000) 模拟耗时,并通过一个回调接口通知登录状态。

最后,Model 层主要是实体数据类和业务逻辑的处理,如 UserBiz 类:

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 UserBiz implements IUserBiz {

@Override
public void login(final String username, final String password, final OnLoginListener loginListener) {
// 模拟子线程耗时操作
new Thread() {

@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 模拟登录成功
if ("Iamasoldier6".equals(username) && "123456".equals(password)) {
User user = new User();
user.setUsername(username);
user.setPassword(password);
loginListener.loginSuccess(user);
} else {
loginListener.loginFailed();
}
}
}.start();
}
}

综上所述,MVC 模式中,View 层即 layout.xml 里的 XML 文件,Model 层即实体数据类 Java Bean 和一些业务逻辑的处理,Controller 层即各式 Activity。本例中,按钮和编辑框是 View 层,用 XML 编写的;用户实体类、设置默认的登录处理以及简单的回调接口是 Model 层;再通过 button.setOnClickListener() 这个 Controller 层的函数连接 MV 层,实现监听用户点击按钮,作出相应的操作。

略作深入思考,我们会发现,XML 文件作为 View 层,能做的事情实在太少。例如,想动态地改变页面的背景,或者动态地隐藏再显示进度圈,均不能在 XML 中解决,只能在 Activity 类中添加相应的代码。如此,便造成了 Activity 既像 Controller,又像 View 的窘境。甚至,若是一个逻辑很复杂的页面,动辄千行,写起来和维护就十分麻烦了。

另外,由模式图得知,View 层和 Model 层是相互交互的,两层之间存在着耦合。耦合对于一个大型程序是很不友好的,开发、测试乃至维护需要花费更多的精力。

MVP 模式

MVP,即 Model View Presenter。正因为 MVC 模式的缺点,才演化出 MVP 模式,实现了 Model 层和 View 层的完全解耦。其中,Model 层和 MVC 的一样,而 Activity 和 Fragment 不再是 Controller 层,而是纯粹的 View 层,所有关于用户事件的分发由 Presenter 层处理。如下图所示:

由图,View 层和 Model 层不再交互,完全解耦。其中,Presenter 层起桥梁的作用,操作 View 层分发的事件传递到 Presenter 层,Presenter 层去操作 Model 层,并将数据返回给 View 层。View 层和 Presenter 层之间并未耦合,通过接口实现两层之间的通信。具体来说,项目中,Activity 和 Fragment 实现定义好的接口,对应的 Presenter 中,通过接口调用方法。此外,我们可以编写测试用的 View,模拟用户的各种操作,以对 Presenter 进行测试。如此,便解决了 MVC 模式中测试和维护难的问题。Demo 地址:MVPLoginDemo,部分代码如下:

首先,XML 文件和 Model 层的代码与 MVC 的一致。

其次,要看的有这样一个接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface IUserLoginView {

String getUsername();

String getPassword();

void clearUsername();

void clearPassword();

void showLoading();

void hideLoading();

void showSuccess(User user);

void showFailedError();
}

View 层和 Presenter 层靠该接口连接。

最后,Presenter 层的代码如下:

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
public class UserLoginPresenter {

private IUserBiz mUserBiz;
private IUserLoginView mUserLoginView;
private Handler mHandler = new Handler();

public UserLoginPresenter(IUserLoginView userLoginView) {
this.mUserLoginView = userLoginView;
this.mUserBiz = new UserBiz();
}

public void login() {
mUserLoginView.showLoading();
mUserBiz.login(mUserLoginView.getUsername(), mUserLoginView.getPassword(), new OnLoginListener() {

@Override
public void loginSuccess(final User user) {
// 需要在 UI 线程中执行
mHandler.post(new Runnable() {

@Override
public void run() {
mUserLoginView.showSuccess(user);
mUserLoginView.hideLoading();
}
});
}

@Override
public void loginFailed() {
// 需要在 UI 线程中执行
mHandler.post(new Runnable() {

@Override
public void run() {
mUserLoginView.showFailedError();
mUserLoginView.hideLoading();
}
});
}
});
}

public void clear() {
mUserLoginView.clearUsername();
mUserLoginView.clearPassword();
}
}

显然,该层即是把 MVC 中 Activity 的部分和 Model 层相关的逻辑剥离出来,从 View 层中获取需要的参数,交给 Model 层执行业务方法,执行过程中需要的反馈和结果以相应的 View 显示。

带来的好处即是,Activity 的代码逻辑减少,View 层和 Model 层完全解耦。此外,可以在 Presenter 层中 Mock 数据,分发给 View 层,以测试布局是否正确。

至此,浅析 Android 中的三大模式:MVC,MVP 和 MVVM (一) 到此结束,(二)将重点剖析近期逐步火起来的 MVVM 模式,未完待续。

本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。

1
Email: [email protected] / WeChat: Wolverine623

您也可以关注我个人的微信公众号码农六哥第一时间获得博客的更新通知,或后台留言与我交流