深入浅出剖析MVP架构模式

来源:互联网 发布:nginx 添加ip黑名单 编辑:程序博客网 时间:2024/05/16 17:23

引言

MVP模式在Android开发领域上据说有着举足轻重的地位,其热度持续发烧,有增无减。作为一名android搬运工,对于mvp架构,我曾经也是一头雾水,这几天才认认真真学习了一把,所以决定把自己所学到的知识整理出来,希望对跟我一样之前不是很懂MVP架构的伙计有一点帮助。相信很多码农都有这样子的习惯,不喜欢看理论,一来就奔代码去,我在这之前也是这样子的,但是有位前辈指导我,要先了解一些架构的思想再去撸代码可能效果会好点,经过我的实验,确实是要先弄懂设计思想再去看代码,思路会清晰很多。好了,言归正传,下面我是按照这样子的思路来写这篇文章的:MVP的架构思想,有什么原则,mvp的设计思路是什么,MVP怎么使用,为什么要推荐用MVP模式,它本身有什么优势跟弊端等这些方面来进行阐述。

  • MVP的简介
  • MVP的架构思想
  • MVP的设计思路
  • MVP的架构原则
  • MVP的使用demo
  • MVP的优势
  • MVP的弊端
  • 列表内容

一、MVP架构简介

相信Android圈子的人都听过无数次MVP模式,那么什么是MVP呢?MVP出现的背景又是什么呢?其实MVP是MVC的变种,其实说白了就是为了解决MVC模式的某些局限而做的一种升级,要说MVP那么就说说MVC。谈起MVC模式,相信大家都耳熟能详,最开始MVC最多用于web应用的开发,后来在移动开发的过程中慢慢地引入了MVC,但是很多公司的项目在使用MVC的时候并没有很好的将三层解耦,很多的数据请求操作仍是在Activity里面执行,造成很多代码的可维护性仍然不高。要知道,在MVC中Activity其实是view层级,但是通常在使用中Activity既是View也是Controller,并没有很好的将View层和controller层进行分离,而且随着UI业务功能日益增强,UI层也履行着越来越多的职责。为了更好地细分视图(View)与模型(Model)的功能,让View专注于处理数据的可视化以及与用户的交互,同时让Model只关系数据的处理,基于MVC概念的变体升级版的MVP(Model-View-Presenter)模式就这样应运而生了。从这里也就可以知道MVP跟MVC的最大区别是:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会从直接Model中读取数据而不是通过 Controller。

二、MVP的架构思想

MVP主要分为三层
Model层,View层,Presenter层
MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是跟MVC一样的Model,在MVP模式中Activity的功能就是响应生命周期和显示界面,具体的其他工作都丢到Presenter中进行完成,而Presenter其实就是Model层跟View层的桥梁。模型图大概如下图所示:
这里写图片描述

三、MVP的设计思路

MVP的设计思路大体是这样子的:

  • View对应于Activity,负责UI的绘制以及与用户进行交互
  • Model依然是业务实体模型
  • Presenter 负责完成View于Model间的交互

四、MVP的架构原则

其实不管MVC还是MVP,甚至是其他的什么架构,我们引入这些结构无非就是为了Activity解耦合,MVP也不例外,就是为了解耦,所以MVP的架构遵循单一职责原则。什么是单一职责原则呢?官方的概念大体翻译过来是:单一职责原则(SRP:Single responsibility principle)又称单一功能原则,其定义为:一个类,应该只有一个可以导致变化的原因(英语不是很好,翻译大体意思就是这样子)。那这个单一职责原则能给我们解决什么问题呢?回顾我们自己的 Android 开发经历,不难总结发现 Activity 类中的代码总会不知不觉地变得很多,这会让读我们代码的人非常痛苦。而造成这种情况的其中一个原因是:Activity 中需要与用户进行大量的交互,用户的操作会改变 Activity 当前显示的界面元素/对应的信息,所以我们总会把 Model、View、点击事件等等……操作全都放到了 Activiy 中,但这样存在一个很严重的问题,无脑地为 Activity 添加代码,势必让 Activity 变得臃肿,结构混乱,职责模糊,特别是之前负责该项目的工程师已经离职,新入职的工程师需要重构该 Activity 时,必将痛不欲生。其实说白了,MVP架构就是以单一职责原则的思想为 Activity 解耦。这是我写的一个MVP小demod的大体结构。
这里写图片描述

从上面这张简单的MVP小demo的流程图,可以看出使用MVP至少要经历一下这些步骤:
(1)创建presenter接口,把所有的业务逻辑的接口都放在这里(注:demo只涉及显示跟隐藏的功能逻辑)并创建它的实现java类PresenterCompl(这里可以方便地查看业务功能,由于接口可以有多种实现方式所以也很方便写单元测试)
(2)创建view接口,把所有视图逻辑的接口都放在这里,其实现类就是当前的Activity/Fragment
(3)有流程图可以看出,Activity包含一个presenter,而presenterCompl又包含一个View接口,并且还依赖了model,Activity最终只保留对presenter接口的调用,其他工作都留在presenterCompl中实现。
(4)model并不是必须的,但是一定要有View跟Presenter。

既然说到单一原则,那么再来说说单一原则有什么好处吧:
实际上,我个人认为设计模式中的思想和生活中高效组织、完成工作的思想都是一致的,因为所谓设计模式,本身就是前人对写代码的经验总结,其目的就在于:提高效率,便于维护,让代码易读,易拓展等等……回顾生活,我们要想让一个团队/组织/公司/企业高效运作,那么这个群体就得根据实际划分部门 —> 确立部门沟通规范 —> 部门内再次进行团队职责细分(例如 UI 部门分为:视觉设计、交互设计等等……) —> 部门成员明确分工,尽可能让整个群体结构呈现为模块化、低耦合、高内聚、职责区分清晰的结构。而单一职责原则体现的也是这样的思想,一个类,应该只受其最根本的抽象逻辑影响,类内的具体变化都应该来自于该抽象逻辑,我们说单一职责,我认为其含义不在于一个类做一件事,而在于一个类就是一个抽象群体,抽象群体具有自身的属性和职责,它的职责可能需要它做很多事,但它的职责始终唯一。进行这样的重构之后我们可以把类内不属于它的逻辑剥离出去,让类遵循它的抽象逻辑,而不需要为其他不属于它的职责增加代码。

五、MVP的小demo示例

1.MVP模式在项目中的一般代码结构如下图demo所示:
这里写图片描述

通过代码结构图可以看到看出MVP结构层级分明(在项目中使用MVP最好通过模块进行分层这样便于管理且结构清晰)
我写的这个mvp小demo就只是一个很简单的小示例,就两个小功能,点击信息显示就显示信息,点击清除按钮就清除信息,效果图如下:

这里写图片描述

这里写图片描述

那么下面看看一下代码的具体结构设计,结构图如上面原则所示的图。

(1)先看View层接口CompanyViewInterface的接口代码:

package com.tianya.wuchunmei.mvpdemo.view;/** * Created by wuchunmei on 2017/7/3. */public interface CompanyViewInterface {    public void onShowInfo(String companyName,String nature,int num);    public void onCleanText();}

从上面代码可以看出,view层接口就两个接口,也就是显示跟隐藏的UI接口。
而CompanyViewInterface接口的实现类就是我们的Activity类,demo中就是MainActivity.java类:

package com.tianya.wuchunmei.mvpdemo;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;import android.widget.Toast;import com.tianya.wuchunmei.mvpdemo.presenter.CompanyPresenterCompl;import com.tianya.wuchunmei.mvpdemo.presenter.CompanyPresenterInterface;import com.tianya.wuchunmei.mvpdemo.view.CompanyViewInterface;public class MainActivity extends AppCompatActivity implements CompanyViewInterface, View.OnClickListener{    private TextView mCompanyName;    private TextView mCompanyNature;    private TextView mNumber;    private Button mShouBtn;    private Button mHideBtn;    private CompanyPresenter mPresenter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initView();        mPresenter = new CompanyPresenterCompl(this);    }    private void initView(){        mCompanyName = (TextView) findViewById(R.id.company_name);        mCompanyNature = (TextView) findViewById(R.id.company_nature);        mNumber = (TextView) findViewById(R.id.number);        mShouBtn = (Button)findViewById(R.id.show);        mHideBtn = (Button)findViewById(R.id.hide);        mShouBtn.setOnClickListener(this);        mHideBtn.setOnClickListener(this);    }    @Override    public void onShowInfo(String companyName, String nature, int num) {        mCompanyName.setText(companyName);        mCompanyNature.setText(nature);        mNumber.setText(String.valueOf(num));    }    @Override    public void onCleanText() {        mCompanyName.setText("");        mCompanyNature.setText("");        mNumber.setText("");    }    @Override    public void onClick(View v) {        int id = v.getId();        switch (id){            case R.id.show:                mPresenter.show();                Toast.makeText(MainActivity.this,"显示信息",Toast.LENGTH_SHORT).show();                break;            case R.id.hide:                mPresenter.hide();                Toast.makeText(MainActivity.this,"清除信息",Toast.LENGTH_SHORT).show();                break;            default:                break;        }    }}

在MainActivity中我们可以看到,MainActivity实现了CompanyViewInterface接口,实现了未实现的方法,在代码中可以看出MainActivity并没有做一些逻辑处理工作,数据处理的工作都是调用CompanyPresenter来完成的。下面就来看看CompanyPresenter的代码设计:

package com.tianya.wuchunmei.mvpdemo.presenter;/** * Created by wuchunmei on 2017/7/3. */public interface CompanyPresenter {    public void show();    public void hide ();}

CompanyPresenter也就两个简单的未实现接口,那再来看看它的实现类呗。

package com.tianya.wuchunmei.mvpdemo.presenter;import com.tianya.wuchunmei.mvpdemo.model.TianyaInfo;import com.tianya.wuchunmei.mvpdemo.view.CompanyViewInterface;/** * Created by wuchunmei on 2017/7/3. */public class CompanyPresenterCompl implements CompanyPresenter{    private CompanyViewInterface mView;    private TianyaInfo mInfo;    public CompanyPresenterCompl(CompanyViewInterface view) {        mView = view;        mInfo = new TianyaInfo("天涯网络技术有限公司","股份制",1000);    }    @Override    public void show() {        String name = mInfo.getCompanyName();        String nature = mInfo.getNature();        int num = mInfo.getNumber();        if(!name.isEmpty() && !nature.isEmpty() && num != 0){            mView.onShowInfo(name,nature,num);        }    }    @Override    public void hide() {        mView.onCleanText();    }}

该实现类也比较简单,定义了一个公司基本信息,然后进行显示和清除的操作。
OK这就完成了最简单的MVP模式了。
下面看看model层的设计,跟MVC一样,就是一个java 实体

package com.tianya.wuchunmei.mvpdemo.model;/** * Created by wuchunmei on 2017/7/3. * 创建一个model类,比如公司的基本信息 */public class TianyaInfo {    private String companyName;    private String nature;    private int number;    public TianyaInfo(String companyName, String nature, int number) {        this.companyName = companyName;        this.nature = nature;        this.number = number;    }    public String getNature() {        return nature;    }    public void setNature(String nature) {        this.nature = nature;    }    public int getNumber() {        return number;    }    public void setNumber(int number) {        this.number = number;    }    public String getCompanyName() {        return companyName;    }    public void setCompanyName(String companyName) {        this.companyName = companyName;    }}

到此,一个MVP的模式大体结构差不多讲完了,我想很多人会有跟我刚开始一样的想法:一个那么简单的功能,有必要这么折腾吗,本来一个类十几行代码就能搞定,现在整出一堆代码,还分那么多个文件,有病吧。嘿嘿,如果有这种想法很正常,因为咱还处于小菜鸟级别,慢慢爬小鬼。当然,我个人还是坚持这样子的观点,不要为了架构而架构,再好的东西不适合你就都不是好东西,所以要从实际出发,看看自己项目的业务逻辑而定。那么接下来进入我们的评判阶段,俗话说,看问题要一分为二,那一起看看MPV的优势跟劣势吧。

六、MVP的优势

1.使Activity中的代码更加简洁,项目结构更加清晰,业务逻辑层次分明
在传统的android项目中,Activity要兼顾着Controller和View,这使得其代码分分钟上千行,给后面的人难以接手以及维护。使用MVP后,Activity就能减肥成功,毕竟现在各行各业都在追求苗条嘛。使用MVP模式,则Activity正如上面你所看到那样,基本上只有findviewbyId,setLisener以及init之类的代码了。其他的就是对presenter的使用,还有对view接口的实现,这样就很轻松看懂代码的逻辑了。而且只要看Presenter的接口,就能明白这个模块都有哪些业务,很快就能定位到具体代码,Activity变得容易看明白,容易维护,以后要调整业务也比较方便。
2、方便单元测试
这个就不用说了,写个junit方法测试的同学都知道,一般单元测试都是用来测试某些新加的业务逻辑,新增的方法接口有没有问题。如果是传统的代码风格,那估计得习惯写了又删,删了又写的悲催过程。MVP中,由于业务逻辑都在presenter里,我们完全可以这么干,写一个PresenterTest的实现类继承presenter接口,那么只要在Activity的创建换成Presenter就能进行测试,测试通过后换回我们原来的presenter实现类就Ok了,免去写了又删,删了又写的苦楚了。
3、避免内存泄漏(这个还没怎么具体实践,理论上是可以的,但是在具体项目中还有待实践)
App发生oom的最大原因可能就是Activity泄漏,对于内存泄漏不清楚的可以看看我之前写的这篇文章:Android 引发内存泄漏类型总结
我们知道Activity是由生命周期的,用户随时可能切换Activity,当App的内存不够用的时候,系统会回收处于后台的Activity的资源以避免oom。传统的模式,一大堆异步任务和对UI的操作都放在Activity里面,这样一来,即使Activity已经被切换到后台,这些异步的任务仍然保留着对Activity实例的引用,这样系统就没法回收这个Activity实例了,结果可能就导致Actiivty leak了。而在Android组件中,Activity对象一般是在堆里占据最多内存,所以系统会先回收Activity对象,如果有Activity Leak ,App很容易为内存不够而oom。采用MVP模式,只要当前的Activity的onDestroy里,分离异步任务对Activity的引用,就方便避免Activity Leak。
4、MVP架构跟大多数架构一样,一般都能使项目解耦,让结构清晰明了,这不多说。

七、MVP的不便之处

事物都是有两面性的,我们看问题要一分为二,不要一味地追求架构而架构,要根据自己的项目实际出发,能给我们项目带来便捷,那我们就要,没什么大的效果那就有选择性使用。
1、使用MVP模式导致代码量变多,文件会大大增加
从上面的demo想必大家都看到了,使用MVP代码量会增加,文件也随之增多,所以一般简单的业务逻辑,我个人是不建议使用MVP的,因为本身业务简单,体现不出MVP的优势,那费那劲去使用MVP干嘛。如果业务逻辑比较复杂,功能模块比较多,那句推荐用MVP模式了,使用MVP结构清晰,查看业务逻辑方便,代码也容易阅读维护。
2、每个view都有个presenter ,类多了。不说presenter重用,实际工作中我是没法抽象使得presenter重用,比如像我这样技术渣,根本就还没达到抽象泛型使用。有时候业务简单就直接一个Activity搞定了。有时候因为业务的关系。。经常一个activity带n个fragment,然后每个都带个Presenter, 有的需要网络请求,有的不需要。。。其实代码量还好,但是。。文件数量大大提升。。。总觉得还是很麻烦。。还有就是service跟广播的生命周期把控也是需要好好斟酌的。

总结:
MVP的优势在项目中是大大体现的,优点大大大于缺点,还算比较完善的美男子啦。MVP让项目结构更清晰了,让自己的思路更清晰。不至于自己的代码,两天后再来看就成了”别人的代码”了。优秀的命名规则加上好的接口设计,可以写很少的注释,别人也能轻易读懂。即便某一模块读不懂,也能知道,这个模块实现了这个功能,先放一放,以后回来再看,不影响撸清业务逻辑。而且解耦明显,容易维护。
这篇文章只是简单分析MVP的基础部分,下次再分析todo-mvp-clean架构以及二者之间的对比。点滴记录,也是成长的一部分!

未完待续。。。。。