前言
在日常开发 APP 的过程中,随着业务的扩展,规模的变化。我们的代码规模也会逐渐变得庞大,每一个类里的代码也会逐渐增多。尤其是 Activity 和 Fragment ,由于 Context 的存在,基本上所有对视图的操作我们只能在 Activity 和 Fragment 中完成;即便是对某些逻辑进行封装,Activity 和 Fragment 依旧会显得过于臃肿。因此,我们需要换一种思路去写代码,这个时候 MVP 模式就应用而生了!那么 MVP 怎么用呢,下面就来说一说。
假设你现在如要实现下图中的功能:
这个需求很简单,就是点击按钮,下载一张图片,显示下载进度;下载完成后,在 ImageView 中显示这张图片。
下面我们就分别用传统的方式(也就是所谓的 MVC) 和 MVP 模式分别取实现这个功能。然后分析一下 MVP 到底好在哪里。
MVC
- public class MVCActivity extends AppCompatActivity {
- private Context mContext;
- private ImageView mImageView;
- private MyHandler mMyHandler;
- private ProgressDialog progressDialog;@Override protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_mvc);
- mContext = this;
- init();
- }
- private void init() { //view init mImageView = (ImageView) findViewById(R.id.image); mMyHandler = new MyHandler(); progressDialog = new ProgressDialog(mContext); progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancle", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { progressDialog.dismiss(); } }); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setTitle("下载文件"); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); //click-event findViewById(R.id.downloadBtn).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { progressDialog.show(); HttpUtil.HttpGet(Constants.DOWNLOAD_URL, new DownloadCallback(mMyHandler)); } }); findViewById(R.id.downloadBtn1).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { progressDialog.show(); HttpUtil.HttpGet(Constants.DOWNLOAD_ERROR_URL, new DownloadCallback(mMyHandler)); } }); } class MyHandler extends Handler { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 300: int percent = msg.arg1; if (percent < 100) { progressDialog.setProgress(percent); } else { progressDialog.dismiss(); Glide.with(mContext).load(Constants.LOCAL_FILE_PATH).into(mImageView); } break; case 404: progressDialog.dismiss(); Toast.makeText(mContext, "Download fail !", Toast.LENGTH_SHORT).show(); break; default: break; } } }}
用 mvc 的方式,一个 Activity 就能搞定。代码逻辑很简单,点击按钮后显示之前初始化好 ProgressDialog,然后开始下载任务(这里 HttpUtil 内部简单封装了 OKHttp 的异步 GET 请求,实现下载文件保存到本地的功能,实现细节在此不做深入探讨,有兴趣的同学可以查看源码),然后将请求结果通过 Handler 返回,在 handleMessage 中根据返回数据的信息做出不同的 UI 处理;下载成功时在 ImageView 中显示图片,下载失败时 Toast 提示。
可以发现,在这种情况之前,Activity 的任务十分繁重,既要负责下载任务的具体实施,还要根据下载进行再次的逻辑判断,才能去更新 UI。这里只是一个简单的任务,你可能觉得无所谓,但是实际开发中,一个 Activity 中有许多的交互事件,这个时候 Activity 的代码就显得特别的庞大;一旦需求变更或出现 bug,那简直就是噩梦一场。
因此,我们希望 Activity 可以变成下面这样
他负责发起处理和用户交互的内容,但又不负责具体的实现; 需要显示什么,不显示什么,什么东西显示多少,有个东西可以直接告诉他, Activity 不再做复杂的逻辑处理;
具体到上面的 demo 里就是,Activity 负责发起下载任务,但是不负责具体实现;什么时候显示 ProgressDialog,显示多少?什么时候提示错误信息,这一切都希望有个东西能直接告诉 Activity,而不再是在 Activity 里再做判断。怎样才能做到呢?那就得靠 MVP 了。
MVP
MVP 模式所做的事情很简单,就是将业务逻辑和视图逻辑抽象到接口中。
怎么理解呢,我们就根据此次要实现的下载功能,用代码说话。
定义 Model,View,Presenter 接口 Model Interface
Model 接口定义所有需要实现的业务逻辑,在我们的下载任务中,业务逻辑只有一个,就是下载;因此 Model 接口可以这么定义 :
- public interface IDownloadModel {
- /** * 下载操作 * @param url */
- void download(String url);
- }
View Interface
View 接口定义所有需要实现的视图逻辑,在我们的下载任务中,视图逻辑包括
- 显示 ProgressDialog;
- 显示 Dialog 具体进度;
- 显示具体的 View(设置图片);
- 显示错误信息(Toast 提示)
因此 View 接口可以这么定义:
- public interface IDownloadView {
- /** * 显示进度条 * @param show */
- void showProgressBar(boolean show);
- /** * 设置进度条进度 * @param progress */
- void setProcessProgress(int progress);
- /** * 根据数据设置view * @param result */
- void setView(String result);
- /** * 设置请求失败时的view */
- void showFailToast();
- }
Presenter Interface
Presenter 接口作为连接 Model 和 View 的中间桥梁,需要将二者连接起来,因此他需要完成以下工作:
- 执行下载任务
- 下载成功返回下载结果
- 下载过程返回下载进度
- 下载失败回调
因此,Presenter 就可以这么定义:
- public interface IDowndownPresenter {
- /** * 下载 * @param url */
- void download(String url);
- /** * 下载成功 * @param result */
- void downloadSuccess(String result);
- /** * 当前下载进度 * @param progress */
- void downloadProgress(int progress);
- /** * 下载失败 */
- void downloadFail();
- }
接口 Model,View,Presenter 具体实现
上面实现了,各个接口的定义,下面来看看他们具体的实现:
Model 具体实现
- public class DownloadModel implements IDownloadModel {
- private IDowndownPresenter mIDowndownPresenter;
- private MyHandler mMyHandler = new MyHandler();
- public DownloadModel(IDowndownPresenter IDowndownPresenter) {
- mIDowndownPresenter = IDowndownPresenter;
- }@Override public void download(String url) {
- HttpUtil.HttpGet(url, new DownloadCallback(mMyHandler));
- }
- class MyHandler extends Handler {@Override public void handleMessage(Message msg) {
- super.handleMessage(msg);
- switch (msg.what) {
- case 300:
- int percent = msg.arg1;
- if (percent < 100) {
- mIDowndownPresenter.downloadProgress(percent);
- } else {
- mIDowndownPresenter.downloadSuccess(Constants.LOCAL_FILE_PATH);
- }
- break;
- case 404:
- mIDowndownPresenter.downloadFail();
- break;
- default:
- break;
- }
- }
- }
- }
在 MVP 模式中,Model 的工作就是完成具体的业务操作,网络请求,持久化数据增删改查等任务。同时 Model 中又不会包含任何 View。
这里 Model 的具体实现很简单,将 Http 任务的结果返回到 Handler 当中,而在 Handler 中的实现又是由 Presenter 完成。
那么 Presenter 接口又是怎样实现的呢?赶紧来看看
Presenter 具体实现
- public class DownloadPresenter implements IDowndownPresenter {
- private IDownloadView mIDownloadView;
- private IDownloadModel mIDownloadModel;
- public DownloadPresenter(IDownloadView IDownloadView) {
- mIDownloadView = IDownloadView;
- mIDownloadModel = new DownloadModel(this);
- }@Override public void download(String url) {
- mIDownloadView.showProgressBar(true);
- mIDownloadModel.download(url);
- }@Override public void downloadSuccess(String result) {
- mIDownloadView.showProgressBar(false);
- mIDownloadView.setView(result);
- }@Override public void downloadProgress(int progress) {
- mIDownloadView.setProcessProgress(progress);
- }@Override public void downloadFail() {
- mIDownloadView.showProgressBar(false);
- mIDownloadView.showFailToast();
- }
- }
可以看到,我们在 DownloadPresenter 的构造方法中,同时实例化了 Model 和 View,这样 Presenter 中就同时包含了两者;
这样;** 在 Presenter 具体实现中,业务相关的操作由 Model 去完成(例如 download),视图相关的操作由 View 去完成
(如 setView 等)**。Presenter 作为桥梁的作用就这样体现出来了,巧妙的将 View 和 Model 的具体实现连接了起来。
View 具体实现
最后再看一下 View 接口的具体实现,也就是 Activity 的实现:
- public class MVPActivity extends AppCompatActivity implements IDownloadView {
- private Context mContext;
- private ImageView mImageView;
- private ProgressDialog progressDialog;
- private DownloadPresenter mDownloadPresenter;@Override protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mContext = this;
- setContentView(R.layout.activity_mvp);
- init();
- }
- private void init() {
- mDownloadPresenter = new DownloadPresenter(this); //view init mImageView = (ImageView) findViewById(R.id.image); findViewById(R.id.downloadBtn).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mDownloadPresenter.download(Constants.DOWNLOAD_URL); } }); findViewById(R.id.downloadBtn1).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mDownloadPresenter.download(Constants.DOWNLOAD_ERROR_URL); } }); progressDialog = new ProgressDialog(mContext); progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancle", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { progressDialog.dismiss(); } }); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setTitle("下载文件"); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); } @Override public void showProgressBar(boolean show) { if (show) { progressDialog.show(); } else { progressDialog.dismiss(); } } @Override public void setProcessProgress(int progress) { progressDialog.setProgress(progress); } @Override public void setView(String result) { Glide.with(mContext).load(result).into(mImageView); } @Override public void showFailToast() { Toast.makeText(mContext, "Download fail !", Toast.LENGTH_SHORT).show(); }}
在点下按钮执行开始下载任务的时候,View(Activity)中没有具体的实现,只是调用了 Presenter 中的 download 方法,而 Presenter 中的 download 又会去调用 Model 的 download 方法,Model 又会在根据具体逻辑(在这里就是 Http 请求)的状态去调用 Presenter 中的方法,例如我们在 handleMessage 方法中,调用 mIDowndownPresenter.downloadProgress(percent) 时,就会去调用 Presenter 的具体实现
- @Override public void downloadProgress(int progress) {
- mIDownloadView.setProcessProgress(progress);
- }
而他的内部实现又是操作具体的 View,也就是我们在 Activity 中初始化 Presenter 中传递的 this,也就是当前 Activity(View),这样最终回到了 Activity 中的
- @Override public void setProcessProgress(int progress) {
- progressDialog.setProgress(progress);
- }
我们为 progressDialog 设置进度。
至此,我们就通过 MVP 的模式实现了我们之前所设想的 Activity
Button 的 click 方法负责发起下载任务,但又不负责具体实现,而是由 Presenter 转接给 Model 去实现 Activity 什么时候显示 ProgressDialog,什么时候显示 Toast 直接由 Presenter 告诉他,他只做一个 View 想做的事情 Activity 里没有任何逻辑处理,所有的逻辑判断都在 Model 中完成了。
这就是 MVP !!!
MVC VS MVP
通过上面的两种实现方案,相信每个人都已经理解了 MVC 和 MVP 的区别;下面就其各自的优缺点再做一下
总结;当然,这里的优缺点只是相对而言。
优点
上面两张图分别是 MVC 和 MVP 架构图。相信许多和我一样尝试去学习和了解 MVP 架构的同学对这两图(或类似的图)并不陌生。
结构更加清晰 *
我们回过头再去看 MVCActivity 的实现,暂且将我们对 Http 请求的封装归结为 Model(M), 那么剩下的就只有 Activity 了, 而这个 Activity 即实现视图逻辑,又需要实现部分业务逻辑,也就是说他既是 Controller(C)又是 View(V)。V 和 C 的划分完全不清晰;因此,传统的代码结构只能勉强称为 MV 或者是 MC,如果算上 xml 的布局文件,才能牵强的称为 MVC 结构。
而 MVP 就不同了,Model,View,Presenter 各司其职,互相搭配,实现了解耦,完全解放了 Activity(或者是 Fragment)。这就是 MVP 的优势,代码结构更加清晰。可以这样说,同一个模块的实现,甚至允许几个人分工完成;假设有一个非常复杂的 Activity,如果使用 MVP 的模式开发;那么这个时候,定义好 MVP 的接口之后,就可以有人专门去做 Model,另一个人专门去做 View;再由一个人写 Presenter 的代码,当然这需要极强的代码规范和协作能力;但这在传统的 MVC 模式中根本是无法想象的,所有的东西都在一个类里,两个人一起改,有了冲突怎么玩 /(ㄒ o ㄒ)/~~。
需求变更,不再是噩梦
假设现在有新的需求,产品经理认为下载失败后只有一个 Toast 提示太单调了(而且用户有可能错过了这 Toast 的显示,而误以为 APP 失去了响应),因此,现在希望在下载失败后弹出一个 Dialog,可以重试下载任务。是想,如果代码使用传统的 MVC 结构,恰巧这个代码不是你写的,或者说就是你写的,但是你已经忘记了具体的逻辑;那么为了实现这个需求你又得去重新捋一遍逻辑,到某个类的 xxx 行进行修改;但是如果使用 MVP 就不同了 View 接口已经定义好了 showFailToast 就是用来显示错误提示的;因此即便代码不是你写的,你都可以很快的找到,应该去哪里改;而省去很多时间。
更容易写单元测试
这个就不展开说了,总之写过单元测试的人应该都有这样的体会。
缺点
MVP 这么好,也不是没有缺点。
如图中所示,使用 MVP 架构之后,多出了许多类;这是必然的;每一个 View(Activity 或 Fragment)都至少需要各自的 Model、Presenter 和 View 接口,在加上他们各自的实现,也就是说每一个页面都会有 6 个 java 文件(算上 Fragment 或 Activity,因为他就是 View 的实现),这样一个稍有点规模的 APP,类就会变得异常的多,而每一个类的加载又会消耗资源;因此,相较于 MVC,这算是 MVP 最大的缺点了吧。
当然,对于这个问题我们可以通过泛型参数、抽象父类的方式,将一些公用的 Model 及 Presenter 抽象出来。这应该就是使用 MVP 架构的精髓了。
最后
个人感觉,使用 MVP 架构是利大于弊的;随着项目规模的增加,代码逻辑的清晰才是最重要的事情。况且 Google 官方也出推出了一系列关于 MVP 的使用 demo。
因此,这也是官方提倡大家使用的。凡事,有利必有弊;类数目的增长是无法避免的事情,因此如何使用泛型和抽象优化 MVP 的结构就变成了我们用好
MVP 的关键了。
当然,我们不能为了 MVP 而去 MVP,如果项目结构不是很庞大,业务不是很复杂;那么传统的 MVC 架构足以,而且也方便!
就爱阅读 www.92to.com 网友整理上传, 为您提供最全的知识大全, 期待您的分享,转载请注明出处。
来源: