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 +
'}';
}
}
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);
}
之后我们的每个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 基础类,提供异步请求,设置监听器方法等
*
* Created by shidong on 15/11/26.
*/
public abstract class BasePresenter<T> implements OnDataListener {
private AsyncTaskManager mAsyncTaskManager;
public BasePresenter() {
//构造方法中初始化AsyncTaskManager
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;
}
}
}
其中我们定义一个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;
/**
* 异步框架类,该类主要实现将耗时操作放在异步线程里面操作,如页面发送请求等。
*
* 发生请求成功: 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> requestMap;
/**
* 构造方法
*
* @param context
*/
private AsyncTaskManager(Context context) {
mContext = context;
mExecutorService = Executors.newFixedThreadPool(MAX_CONNECTIONS_NUM);
requestMap = new WeakHashMap>();
}
/**
* 单例模式得到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);
//after version 2.3 added executeOnExecutor method.
//before 2.3 only run five asyntask, more than five must wait
if (Build.VERSION.SDK_INT >= 11) {
mAsynctask.executeOnExecutor(mExecutorService);
} else {
mAsynctask.execute();
}
requestMap.put(requestCode, new WeakReference(mAsynctask));
} else {
NLog.e(tag, "the error is requestCode < 0");
}
}
/**
* 根据requestCode取消请求
*
* @param requestCode
*/
public void cancelRequest(int requestCode) {
WeakReference 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>> it = requestMap.entrySet().iterator();
while (it.hasNext()) {
Entry> entry = (Entry>) it.next();
Integer requestCode = entry.getKey();
cancelRequest(requestCode);
}
requestMap.clear();
}
}
}
这回好了,异步请求方法有了,我们就大胆的在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);
}
}
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);
//MVP模式
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();
}
});
//MVC模式
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, "登录失败");
}
}
至此,我们的整个流程已经完成,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) {
}
}
所有的请求和显示逻辑在Activity中完成,目测比MVP写法多了一倍的代码,还好我们已经将异步请求逻辑封装,否则可能会更多;
这样两种方法我们都可以运用自如, 在适当的时候选择适当的方法来完成.
好了,到这里已经写完我想要写的东西,个人水平有限,可能理解不够深刻,望多多讨论和提出更好建议.最后感谢hongyang大神的文章,让我进一步完善了项目总的请求封装逻辑.
参考文章 http://blog.csdn.net/lmj623565791/article/details/46596109
完整Demo代码请点击 http://download.csdn.net/detail/ronaldong99/9303337