Android MVP架构和MVC架构比较
代码示例请点击点击下载demo
1.概述 如题,本文想要讨论的是MVP与MVC之间的比较,那么在这之前,我们首先来回顾一下MVC的概念.MVC我们再熟悉不过,即Model-View-Controllor,对应于Android项目结构如下
- Model对应于业务逻辑和实体类 - View对应于xml布局文件 - Controller对应于Activity
这里我们的View布局文件只做了界面的展示工作,做的工作其实并不多,当然现在Android有了MVVM来解决这个问题,让View变的更加强大,我们这里暂且不做讨论.而界面的大部分显示逻辑和数据处理逻辑都是在Activity中完成的(类似于ios中的ViewControllor类),这就使我们的Activity即像Controllor又像View,导致Activity中的代码变的异常复杂,我本人以前接手的一个项目MainActivity中有超过3000行的代码,导致后期根本无法维护,只能重写来解决.
而随着MVP架构的出现,我们可以将以往在Activity中完成的数据展示逻辑到Presenter中完成,对应的架构就变成了下边这样
- Model不变对应于业务逻辑和实体类 - View对应于Activity,负责界面的展示 - Presenter,负责业务逻辑处理,来连接Model和View
2.MVP与MVC对比
下边用一张图来更清晰的对比两种逻辑
可以看到,二者最明显的区别就是,MVC允许View和Model进行数据交互,而MVP则是把View和Model的交互交给Presenter来完成,这样就使得Activity中的变的更轻,代码更加清爽简洁,耦合度也更低,便于后期的维护.
通过代码来展示二者在设计上的一些区别
3.代码设计
1.包结构 这里通过一个模拟登录的例子来演示二者在设计上的异同,首先我们设计一下程序的包结构,一个好的包结构是程序的基石.贴出一张图来展示一下我的Demo程序包结构
是的,看上去很简单,就是model-view-presenter,让人一目了然.接下来我们来分析一下具体的业务逻辑,并且来编写代码.
1.Action层 首先是实体类,这个必不可少,不做讨论
/** * 登录返回用户信息 * Created by shidong on 15/11/26. */public class UserInfo implements Serializable { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "UserInfo{" + "name='" + name + '\'' + ", age=" + age + '}'; }}
- 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
2.Action中的登录操作,我们就叫login()方法
/** * 具体实现登录操作(项目中一般是网络请求返回数据) * Created by shidong on 15/11/26. */public class LoginAction { public UserInfo login() { UserInfo userInfo = new UserInfo(); userInfo.setName("dong"); userInfo.setAge(20); return userInfo; }}
好了,到这里我们的action层已经编写完了,跟以往的写法没有半点区别.
2.Presenter层
可以试想一下,我们的登录操作通常会有什么呢,无非就是在异步线程中请求登录操作,即上边的LoginAction,然后处理登录成功和失败的逻辑.那么好,我们的登录功能接口定义大概就是下边这样子
/** * 登录接口 * Created by shidong on 15/11/26. */public interface OnLoginListener { public void loginSuccess(UserInfo userInfo); public void loginFailure();}
为了演示方便,暂且只定义这两个方法.
现在应该设计我们的Presenter类了,嗯,很显然,我们需要有一个login()方法,这个login调用LoginAction中的方法完成登录请求.应该是这样的
public void login() { request(REQ_LOGIN); }
嗯?request()方法是什么东东,好了,接下来继续分析.别忘了,这些请求一定是在异步线程中实现的,耗时操作都是需要这个异步请求操作的,Android已经为我们提供了AsyncTask,我们来对其进一步封装.既然是公用的,我们需要定义一个接口来完成上述操作,接口中方法可以仿照AsyncTask类设计如下
/** * [异步请求监听] * * @author mashidong * @version V3.6.0 * @date 2015-11-25 * **/public interface OnDataListener { /** * 耗时操作将在该方法中实现,比如发网络请求等 * @param requestCode 请求code * @return * @throws HttpException */ public Object doInBackground(int requestCode) throws HttpException; /** * 请求成功回调方法 * @param requestCode 请求code * @param result 结果 */ public void onSuccess(int requestCode, Object result); /** * 请求失败回调方法 * @param requestCode 请求code * @param state 状态 * @param result 结果 */ public void onFailure(int requestCode, int state, Object result);}
- 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
之后我们的每个Presenter都需要实现这个接口来完成异步请求任务,谁都不想每次都去写重复的代码,显然我们需要定义一个BasePresenter来完成这些操作.
package com.example.shidong.androidmvp.presenter;import com.example.shidong.DemoApplication;import com.example.shidong.network.async.AsyncTaskManager;import com.example.shidong.network.async.OnDataListener;import com.example.shidong.network.http.HttpException;import com.example.shidong.network.utils.NToast;/** * Presenter 基础类,提供异步请求,设置监听器方法等 * <p> * Created by shidong on 15/11/26. */public abstract class BasePresenter<T> implements OnDataListener { private AsyncTaskManager mAsyncTaskManager; public BasePresenter() { this.mAsyncTaskManager = AsyncTaskManager.getInstance(DemoApplication.application); } /** * 发送请求,默认是需要检查网络的 * * @param requsetCode 请求code */ public void request(int requsetCode) { request(requsetCode, true); } /** * 发送请求 * * @param requsetCode 请求code * @param isCheckNetwork 是否需要检查网络, true需要,false不需要 */ public void request(int requsetCode, boolean isCheckNetwork) { mAsyncTaskManager.request(requsetCode, isCheckNetwork, this); } /** * 取消请求 * * @param requsetCode */ public void cancelRequest(int requsetCode) { mAsyncTaskManager.cancelRequest(requsetCode); } /** * 取消所有请求 */ public void cancelRequest() { mAsyncTaskManager.cancelRequest(); } /** * 设置listener * * @param listener */ public abstract void setListener(T listener); @Override public Object doInBackground(int requestCode) throws HttpException { return null; } @Override public void onSuccess(int requestCode, Object result) { } @Override public void onFailure(int requestCode, int state, Object result) { switch (state) { case AsyncTaskManager.HTTP_NULL_CODE: NToast.shortToast(DemoApplication.application, "网络不可用"); break; case AsyncTaskManager.REQUEST_ERROR_CODE: break; case AsyncTaskManager.HTTP_ERROR_CODE: NToast.longToast(DemoApplication.application, "网络连接错误"); break; } }}
- 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
其中我们定义一个AsyncTaskManager来对异步请求进行管理.具体见代码
package com.example.shidong.network.async;import android.content.Context;import android.os.Build;import com.example.shidong.network.utils.NLog;import java.lang.ref.WeakReference;import java.util.Iterator;import java.util.Map;import java.util.Map.Entry;import java.util.WeakHashMap;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 异步框架类,该类主要实现将耗时操作放在异步线程里面操作,如页面发送请求等。 * <p/> * 发生请求成功: REQUEST_SUCCESS_CODE = 200 * 发生请求失败: REQUEST_ERROR_CODE = -999 * 网络有问题: HTTP_ERROR_CODE = -200 * 网络不可用: HTTP_NULL_CODE = -400 * * @author mashidong * @version V3.6.0 * @date 2015-11-25 **/public class AsyncTaskManager { /** * 日志对象 **/ private final String tag = AsyncTaskManager.class.getSimpleName(); /** * 发生请求成功 **/ public static final int REQUEST_SUCCESS_CODE = 200; /** * 发生请求失败 **/ public static final int REQUEST_ERROR_CODE = -999; /** * 网络有问题 **/ public static final int HTTP_ERROR_CODE = -200; /** * 网络不可用 **/ public static final int HTTP_NULL_CODE = -400; /** * 默认下载请求码 **/ public static final int DEFAULT_DOWNLOAD_CODE = 10000; /** * 线程池最多线程数 **/ public final int MAX_CONNECTIONS_NUM = 10; private Context mContext; private static AsyncTaskManager instance; private static ExecutorService mExecutorService; private static Map<Integer, WeakReference<BaseAsyncTask>> requestMap; /** * 构造方法 * * @param context */ private AsyncTaskManager(Context context) { mContext = context; mExecutorService = Executors.newFixedThreadPool(MAX_CONNECTIONS_NUM); requestMap = new WeakHashMap<Integer, WeakReference<BaseAsyncTask>>(); } /** * 单例模式得到AsyncTaskManager实例对象 * * @param context * @return */ public static AsyncTaskManager getInstance(Context context) { if (instance == null) { synchronized (AsyncTaskManager.class) { if (instance == null) { instance = new AsyncTaskManager(context); } } } return instance; } /** * 发送请求, 默认是需要检查网络的 * * @param requestCode 请求code * @param listener回调 */ public void request(int requestCode, OnDataListener listener) { request(requestCode, true, listener); } /** * 发送请求 * * @param requestCode 请求code * @param isCheckNetwork 是否需要检查网络 * @param listener 回调 */ public void request(int requestCode, boolean isCheckNetwork, OnDataListener listener) { DownLoad bean = new DownLoad(requestCode, isCheckNetwork, listener); if (requestCode > 0) { BaseAsyncTask mAsynctask = new BaseAsyncTask(bean, mContext); if (Build.VERSION.SDK_INT >= 11) { mAsynctask.executeOnExecutor(mExecutorService); } else { mAsynctask.execute(); } requestMap.put(requestCode, new WeakReference<BaseAsyncTask>(mAsynctask)); } else { NLog.e(tag, "the error is requestCode < 0"); } } /** * 根据requestCode取消请求 * * @param requestCode */ public void cancelRequest(int requestCode) { WeakReference<BaseAsyncTask> requestTask = requestMap.get(requestCode); if (requestTask != null) { BaseAsyncTask request = requestTask.get(); if (request != null) { request.cancel(true); request = null; } } requestMap.remove(requestCode); } /** * 取消所有请求 */ public void cancelRequest() { if (requestMap != null) { Iterator<Entry<Integer, WeakReference<BaseAsyncTask>>> it = requestMap.entrySet().iterator(); while (it.hasNext()) { Entry<Integer, WeakReference<BaseAsyncTask>> entry = (Entry<Integer, WeakReference<BaseAsyncTask>>) it.next(); Integer requestCode = entry.getKey(); cancelRequest(requestCode); } requestMap.clear(); } }}
- 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
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
这回好了,异步请求方法有了,我们就大胆的在Presenter中完成我们的操作吧.
package com.example.shidong.androidmvp.presenter;import com.example.shidong.androidmvp.presenter.impl.OnLoginListener;import com.example.shidong.androidmvp.module.action.LoginAction;import com.example.shidong.androidmvp.module.model.UserInfo;import com.example.shidong.network.http.HttpException;/** * 登录Presenter 实现登录逻辑 * Created by shidong on 15/11/26. */public class LoginPresenter extends BasePresenter<OnLoginListener> { private static final int REQ_LOGIN = 0x0a; private OnLoginListener loginListener; @Override public void setListener(OnLoginListener listener) { this.loginListener = listener; } public void login() { request(REQ_LOGIN); } @Override public Object doInBackground(int requestCode) throws HttpException { switch (requestCode) { case REQ_LOGIN: LoginAction action = new LoginAction(); return action.login(); default: return null; } } @Override public void onSuccess(int requestCode, Object result) { super.onSuccess(requestCode, result); switch (requestCode) { case REQ_LOGIN: if (result != null) { UserInfo userInfo = (UserInfo) result; if (loginListener != null) { loginListener.loginSuccess(userInfo); } } default: break; } } @Override public void onFailure(int requestCode, int state, Object result) { super.onFailure(requestCode, state, result); }}
- 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
ok,到这里,我们所有的异步请求逻辑已经完成的,好像还少点什么,不错,最后要在我们的View中调用请求方法来完成显示
3.Activity设计
package com.example.shidong.androidmvp.view;import android.content.Intent;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.support.v7.widget.Toolbar;import android.view.View;import com.example.shidong.androidmvp.R;import com.example.shidong.androidmvp.presenter.impl.OnLoginListener;import com.example.shidong.androidmvp.module.model.UserInfo;import com.example.shidong.androidmvp.presenter.LoginPresenter;import com.example.shidong.network.utils.NToast;public class LoginActivity extends AppCompatActivity implements OnLoginListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test_mvp); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); findViewById(R.id.btn_login).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { LoginPresenter presenter = new LoginPresenter(); presenter.setListener(LoginActivity.this); presenter.login(); } }); findViewById(R.id.btn_test_mvc).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { LoginActivity.this.startActivity(new Intent(LoginActivity.this, MVCLoginActivity.class)); } }); } @Override public void loginSuccess(UserInfo userInfo) { NToast.longToast(this, "登录成功" + userInfo.toString()); } @Override public void loginFailure() { NToast.longToast(this, "登录失败"); }}
- 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
至此,我们的整个流程已经完成,Activity中只有区区几十行代码,处理几个回调方法即可,是不是很清爽,而且所有的逻辑都做了很好的分层,便于代码阅读和后期维护.当然这种写法带来的代价就是会增加很多类和接口的定义,所以我本人建议适当使用,即能用则用,简单的逻辑可以不用.比如上述登录请求,直接用MVC来完成是下边这样
package com.example.shidong.androidmvp.view;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.support.v7.widget.Toolbar;import android.view.View;import com.example.shidong.androidmvp.R;import com.example.shidong.androidmvp.module.action.LoginAction;import com.example.shidong.androidmvp.module.model.UserInfo;import com.example.shidong.network.async.AsyncTaskManager;import com.example.shidong.network.async.OnDataListener;import com.example.shidong.network.http.HttpException;import com.example.shidong.network.utils.NToast;public class MVCLoginActivity extends AppCompatActivity implements OnDataListener { private static final int REQ_MVCLOGIN = 0x0b; private AsyncTaskManager mAsyncTaskManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_mvclogin); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); mAsyncTaskManager = AsyncTaskManager.getInstance(this); findViewById(R.id.btn_mvclogin).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { request(REQ_MVCLOGIN); } }); } /** * 发送请求,默认是需要检查网络的 * * @param requsetCode 请求code */ public void request(int requsetCode) { request(requsetCode, true); } /** * 发送请求 * * @param requsetCode 请求code * @param isCheckNetwork 是否需要检查网络, true需要,false不需要 */ public void request(int requsetCode, boolean isCheckNetwork) { mAsyncTaskManager.request(requsetCode, isCheckNetwork, this); } /** * 取消请求 * * @param requsetCode */ public void cancelRequest(int requsetCode) { mAsyncTaskManager.cancelRequest(requsetCode); } /** * 取消所有请求 */ public void cancelRequest() { mAsyncTaskManager.cancelRequest(); } @Override public Object doInBackground(int requestCode) throws HttpException { LoginAction action = new LoginAction(); return action.login(); } @Override public void onSuccess(int requestCode, Object result) { UserInfo userInfo = (UserInfo) result; NToast.longToast(this, userInfo.toString()); } @Override public void onFailure(int requestCode, int state, Object result) { }}
- 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
所有的请求和显示逻辑在Activity中完成,目测比MVP写法多了一倍的代码,还好我们已经将异步请求逻辑封装,否则可能会更多;
这样两种方法我们都可以运用自如, 在适当的时候选择适当的方法来完成.
好了,到这里已经写完我想要写的东西,个人水平有限,可能理解不够深刻,望多多讨论和提出更好建议.最后感谢hongyang大神的文章,让我进一步完善了项目总的请求封装逻辑.
参考文章 http://blog.csdn.net/lmj623565791/article/details/46596109
完整Demo代码请点击 http://download.csdn.net/detail/ronaldong99/9303337