MVP模式从入门到精通

MVP模式从入门到精通首先附上自己写的一个MVP的demo,这是一个很标准的MVP,Github地址如下:https://github.com/SilasGao/MVPDemo首先MVP是从经典的MVC架构演变而来,那我们是不是要先说下何为MVC模式?系统C/S(Client/Server)三层架构模型:1)视图层(View):一般采用XML文件对应用的界面进行描述,使用的时候可以直接引入…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

首先附上自己写的一个MVP的demo,这是一个很标准的MVP,Github地址如下:

https://github.com/SilasGao/MVPDemo

 

首先MVP 是从经典的MVC架构演变而来,那我们是不是要先说下何为MVC模式?

 

系统C/S(Client/Server)三层架构模型:

1)视图层(View):一般采用XML文件对应用的界面进行描述,使用的时候可以直接引入,极为方便,可以的大大缩短开发时间,也可以使用JavaScript+HTML等的方式作为View层,当然这里需要进行Java和JavaScript之间的通信,幸运的是,Android提供了非常方便的通信实现。业务逻辑层(BLL):它的关注点主要集中在业务规则的制定、业务流程的实现等与业务需求有关的系统设计,也即是说它是与系统所应对的领域(Domain)逻辑有关,很多时候,也将业务逻辑层称为领域层。

2)控制层(Controller):Android的控制层的重任通常落在了众多的Acitvity的肩上,这句话也就暗含了不要在Acitivity中写代码,要通过Activity交割Model业务逻辑层处理。

3)模型层(Model):对数据库的操作、以及其他和数据有关的的操作都应该在Model里面处理,当然对业务计算等操作也是必须放在的该层的。就是应用程序中二进制的数据。

   三层结构架构三层间的交互及作用如下图所示:

MVP模式从入门到精通

 

传统的MVC模式是很不错,我们也很熟悉,毕竟用了这么多年了。(突然想到之前去一家公司面试他问个HelloWord的Android项目是不是MVC,我说不是,他说回答错误,阿西吧!)

在Android项目上你会发现Activity的责任太重,什么东西都要放在Activity中,最终导致了Activity太过臃肿。虽然能抽的都抽出来了,但是会发现代码还是很多,试想下上千行代码还没有注释,能不晕?即使是自己写的,过些日子去看也有些晕晕的吧?

尤其代码敲完,一个月后需求又改了,从600、700行代码中找到要修改的地方也是要一点功夫的。

为了给Activity减轻压力,这时候MVP出现了!

 

MVP有什么好处,为什么要用MVP呢?

网上搜下一大堆MVP的各种好处,本人总结下主要有以下几点:

 

  • 代码解耦
  • 结构清晰
  • 可复用
  • 扩展性高
  • 方便进行单元测试

在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会从直接Model中读取数据而不是通过 Controller。

在MVP里,Presenter完全把Model和View进行了分离,主要的程序逻辑在Presenter里实现。而且,Presenter与具体的View是没有直接关联的,而是通过接口进行交互,从而使得在变更View时候可以保持Presenter的不变,可以多次复用。

在MVP里,应用程序的逻辑主要在Presenter来实现,其中的View是很薄的一层,只应该有简单的Set/Get的方法,用户输入和设置界面显示的内容,除此就不应该有更多的内容,绝不容许直接访问Model。

MVP主要解决就是把逻辑层抽出来成P层,要是遇到需求逻辑上的更改就可以只需要修改P层了或者遇到逻辑上的大改我们可以直接重写一个P也可以,很多开发人员把所有的东西都写在了Activity/Fragment里面这样一来遇到频繁改需求或者逻辑越来越复杂的时候,Activity /Fragment里面就会出现过多的混杂逻辑导致出错,所以MVP模式对于APP来对控制逻辑和UI的解耦来说是一个不错的选择。

 

 

上面说逻辑是在Presenter中处理的,假设这是一个登陆界面,如要输入账号密码,那么对账号密码的是否为空判断以及正则表达式判断等也要放在Presenter中,毕竟这些都是逻辑。

然后判断都成功的话(下面的操作都是在Presenter中进行的),先调用view层的方法,让ProgressDialog显示出来,然后调用model层的网络请求,结果的话在presenter中回调。在回调里面分别做ProgressDialog消失的处理,然后成功的话调用view层的方法,进入主界面。失败的话调用view层的方法,提示失败Toast之类的。

这就是最简单的一个流程了。

 

 

MVP是什么?怎么实现?

 

下图是本人Demo中的代码,就一个页面而已,然而一个页面竟然有这么多的类,11个,我勒个去,吓死宝宝了,这么多的类,说实话一个类就能解决了。不慌,下面让我们仔细分析为什么这么多类

MVP模式从入门到精通

首先介绍下每个类是干嘛的:

  • BuyBooActivity是我们的Activity
  • BaseActivity是Activity的基类
  • BasePresenter是Presenter的基类
  • BuyBookBean是我们的bean,也就是传说中的实体类,几个成员变量,自动生成一堆get、set方法的那个类
  • IBuyBookView也就是BuyBooActivity的接口
  • BuyBookPresenter也就是这个Actvity的Presenter
  • IBuyBookPresenter是BuyBookPresenter的接口
  • BuyBookModel是这个Activity的数据层
  • IBuyBookModel是BuyBookModel的接口
  • BuyBookAdapter是BuyBooActivity里面ListView的适配器
  • ValueCallBack,是一个通用的回调接口

这里接口都以I开头,每个model,presenter,还有Activity的view,都对应着一个接口,有些人可能会问了,干嘛写接口,一下多了这么多类,看的都懵逼了(比如以前有一个同事,他一直说项目用MVP,但是他不知道什么是MVP,直接把数据层写在了P中,估计是他以为bean才是model吧。我也不跟他说,毕竟人家工资比较高。说道这里突然想起了他以前的MVVM模式,第一个M是bean,他说这是model,第二个M以及一个V他说这是viewmodel,结果放的是所有的网络请求,最后一个V他放的是Aciivty和Fragment了。好了笑话说到这里,继续我们的主题)

 

为什么写接口?

问这个问题的童鞋应该去复习设计模式的六大基本原则了,我们要控制项目而不是项目控制我们,我们要进行的是面向接口的编程,用抽象(或者接口)搭建框架,用实现扩展细节。当然接口也不能滥用,都要看具体情况,这里本人是按照最高标准来写的。

 

Google官方MVP示例项目是把上面的IBuyBookPresenter以及IBuyBookView这2个接口放到了IBuyBookContract这个协议类中,实际上是一样的,这就看个人喜好了,各有各的优缺点。比如把本栗子的代码改成有协议类的这种就如下代码所示,看到了吧,其实是一样的。

public interface IBuyBookContract
{
    interface IBuyBookView
    {
        void showToast(String msg);

        void refreshAdapter();

        void onEmpty();
    }

    interface IBuyBookModel
    {

        List<BuyBookBean> getAdapterData();
    }
}

Jetbrains全家桶1年46,售后保障稳定

 

M(Model)

数据层,和MVC中的M一样,用来放数据的处理(比如网络请求,缓存等)。

 

V(View)

负责UI具体实现展现。比如Presenter派发过来一个动作是showDialog显示进度命令,那么我们这个View就负责实现具体UI。

 

P(Presenter)

负责处理业务逻辑代码,处理Model数据,然后将处理完的数据分发到View层。

 

 

下面开始贴代码讲解

 

先来讲View IBuyBookView

这里主要是包含了Activity的一系列关于UI操作,然后用我们的Activity是实现,这样Presenter就可以调用了。

 

public interface IBuyBookView {
    /**
     * 提示toast
     */
    void showToast(String msg);

    /**
     * 刷新adapter
     */
    void refreshAdapter();

    void onEmpty();
}

接下来讲Model IBuyBookModel BuyBookModel

 

主要是写了几个方法供BuyBookModel去实现

 

public interface IBuyBookModel {
    /**
     * 获取模拟数据
     */
    void getTestData(ValueCallBack<List<BuyBookBean>> callBack);


    /**
     * 返回本地adapter数据
     * @return
     */
    List<BuyBookBean> getAdapterData();
}

 

这里实现了IBuyBookModel,然后模拟了下网络请求,用随机来模拟请求成功与失败,所以如果有童鞋下载Demo发现进去怎么空白一片,那么请退出来多近几次,本人曾连续5次进去都是空白的,这运气也太背了吧。

 

public class BuyBookModel implements IBuyBookModel {
    private List<BuyBookBean> listData;


    public BuyBookModel() {
        this.listData = new ArrayList<>();
    }

    @Override
    public void getTestData(final ValueCallBack<List<BuyBookBean>> callBack) {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                List<BuyBookBean> list = new ArrayList<>();
                list.add(new BuyBookBean("赵云", 1, "09-27 09:11"));
                list.add(new BuyBookBean("赵云、隔壁老王、小王、典韦、貂蝉、林芳、曹操、刘备、关羽、黄忠、张飞、诸葛孔明", 10, "09-27 09:11"));
                list.add(new BuyBookBean("黄忠、孙权、大乔", 50, "09-27 09:11"));
                list.add(new BuyBookBean("大乔、小乔、貂蝉、孙尚香", 300, "09-27 09:11"));

                Random rd = new Random();
                int N = rd.nextInt(10);
                if (N > 5) {
                    callBack.onSuccess(list);
                } else {
                    callBack.onFail("拒绝请求");
                }
            }
        }, 1000);
    }


    @Override
    public List<BuyBookBean> getAdapterData() {
        return listData;
    }
}

 

这里贴上楼上那个回调接口,这里用了泛型,为什么呢?为了多多复用。

 

public interface ValueCallBack<T> {
    void onSuccess(T t);

    void onFail(String code);
}

接下来讲Presenter BasePresenter IBuyBookPresenterBuyBookPresenter

这个是所有Presenter的基类,里面有个initData()方法,基本每个Presenter都要处理网络请求吧,所以我就弄了这么一个基类,至于为什么是抽象的而不是接口,这是因为抽象方便点吧,如果我们往抽象类中添加新的方法(该方法不是抽象的),可以给他提供默认的实现,而且不要修改现有的代码,但是如果是接口的话,就要修改现有的代码了。指不定我们以后要往这里加什么呢。

这里为什么用了个泛型?为了让人一看这个Presenter就知道这对应着哪个Activity,实际上这可以不加的,但是我觉得加上去更好。便于后来人也便于自己以后再来修改这个类。

 

public abstract class BasePresenter<T extends BaseActivity> {

    abstract void initData();
}

 

 

 

这里主要写了个方法,以供BuyBookPresenter实现

 

public interface IBuyBookPresenter {

    List<BuyBookBean> getAdapterData();
}

 

这里首先实现现了IBuyBookPresenter继承了BasePresenter,然后重写一些方法。这里的构造方法是重点,在构造方法中,我们需要传入一个IBookView,实际上我们的Activity已经实现IBookView了,所以这里实际上传的是具体的Activity,也就是this就行了。然后model我们可以直接new出来用,这里就new了。

在initData中我们是进行了具体的网络请求,网络请求我们是不是要弹一个Dialog出来,直接在这mView.loading();调用就行了。然后请求成功onSuccess()里面让Dialog消失,提醒适配器刷新。失败的话onFail()里面提示Dialog消失,然后ListView设置失败页面什么的。

 

public class BuyBookPresenter extends BasePresenter<BuyBookActivity> implements IBuyBookPresenter {

    private IBuyBookView  mView;
    private IBuyBookModel mModel;

    public BuyBookPresenter(IBuyBookView iBuyBookView) {
        this.mView = iBuyBookView;
        this.mModel = new BuyBookModel();
    }


    @Override
    public List<BuyBookBean> getAdapterData() {
        return mModel.getAdapterData();
    }

    @Override
    public void initData() {
        //在这里弹出loading
        mModel.getTestData(new ValueCallBack<List<BuyBookBean>>() {
            @Override
            public void onSuccess(List<BuyBookBean> buyBookBeen) {
                //在这里取消loading
                //简单数据操作可以在presenter里完成
                mModel.getAdapterData().addAll(buyBookBeen);
                mView.refreshAdapter();
            }

            @Override
            public void onFail(String code) {
                //在这里取消loading
                mView.showToast(code);
                mView.onEmpty();
            }
        });
    }
}

接下来讲我们的Activity BuyBookActivity BaseActivity

这是我们Acticity的基类,可以看到这里有个泛型,注意了,前方高能。这个泛型还必须继承BasePresenter,这个首先为了让人一看到这个Activity就知道对应着那个Presenter;其次最重要的就是为了下面那个成员变量basepresenter,我们写一个抽象的方法要求返回泛型T,而这个泛型T又继承了BasePresenter,那么我们就得到了具体Presenter的成员变量,可以直接用这个成员变量来调用Presenter中的方法了。

 

public abstract class BaseActivity<T extends BasePresenter> extends Activity {
    
    protected T basepresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayout());
        initView();
        basepresenter = initPresent();
        onPrepare();
    }

    abstract T initPresent();

    abstract int getLayout();

    abstract void initView();

    abstract void onPrepare();
}

这个就是最终具体的Activity了,可以看到这里都没什么逻辑,基本都是一些重写的方法。

 

public class BuyBookActivity extends BaseActivity<BuyBookPresenter> implements IBuyBookView
{

    private ListView mListView;
    private BuyBookAdapter mAdapter;

    @Override
    BuyBookPresenter initPresent()
    {
        return new BuyBookPresenter(this);
    }

    @Override
    int getLayout()
    {
        return R.layout.activity_main;
    }

    @Override
    void initView()
    {
        mListView = (ListView) findViewById(R.id.listview);
    }

    @Override
    void onPrepare()
    {
        mAdapter = new BuyBookAdapter(this, basepresenter.getAdapterData());
        mListView.setAdapter(mAdapter);
        basepresenter.initData();
    }

    @Override
    public void showToast(String msg)
    {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void refreshAdapter()
    {
        mAdapter.notifyDataSetChanged();
    }

    public void onEmpty()
    {
        mListView.setEmptyView(null);
    }
}

 

 

 

 

 

最后虽然MVP模式有许多好处,但是有一个致命的缺点就是类太多,本来一个类最多变成了7个类,最少变成6个类(使用Contract协议类)。所以并不是所有的页面都要用MVP模式的,很简单的页面就没必要了,浪费时间是不是。

 

为什么MVP模式利于单元测试?

 

 

Presenter将逻辑和UI分开了,里面没有Android代码,都是纯纯的java代码。我们可以直接对Presenter写Junit测试

 

 

 

最后:这个MVP模式是非常基础了,只是给大家提供一下这个思路。

因为博客平时不是经常上,所以有问题需要及时回答的大佬们可以扫下这个。

MVP模式从入门到精通

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/226890.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • linux redis重启,互联网常识:linux下重启redis的方法

    linux redis重启,互联网常识:linux下重启redis的方法跟大家讲解下有关 linux 下重启 redis 的方法 相信小伙伴们对这个话题应该也很关注吧 现在就为小伙伴们说说 linux 下重启 redis 的方法 小编也收集到了有关 linux 下重启 redis 的方法的相关资料 希望大家看到了会喜欢 导语 已经将 redis 加入到 etc 下此时服务器启动 redis 也启动但是却连不上 redis 所有有了以下的过程 学习视频分享 redis 视频教程 查看 redis 状态 syst

    2025年6月1日
    4
  • 博客群发软件–用 Windows Live Writer完美发布新浪、网易、blogcn、blogbus、cnbl

    博客群发软件–用 Windows Live Writer完美发布新浪、网易、blogcn、blogbus、cnbl前言:当今网络博客、微薄铺天盖地,相信即使一个普通的用户也都注册了很多家品牌的博客或者微薄等,那么困扰着大家一个很大的问题,同时在多家博客发布同样的内容,如果说只是简单的文字还好说,复制粘贴就完事了,但是如果里面包含着图片,那么使用复制粘贴是不可以的,因为诸多博客品牌之间图片是不能共享使用的。研究了一天,终于找到较为完美博客群发软件,那就是微软出品的…

    2022年7月13日
    33
  • er图和uml图_数据库表结构er图

    er图和uml图_数据库表结构er图ER图:实体-联系图(Entity-RelationDiagram)用来建立数据模型,在数据库系统概论中属于概念设计阶段,ER图提供了表示实体(即数据对象)、属性和联系的方法,用来描述现实世界的概念模型构成E-R图的基本要素是实体、属性和联系,其表示方法为:实体型:用矩形表示,矩形框内写明实体名;属性:用椭圆形或圆角矩形表示,并用无向边将其与相应的实体连接起来;多值属性由双线连接;主属性名称下加…

    2025年12月11日
    3
  • js弹出确认取消对话框_vs点击按钮弹出对话框

    js弹出确认取消对话框_vs点击按钮弹出对话框if(window.confirm(‘你确定要执行删除操作吗?’)){alert(“您点击了确定”);}else{alert(“您点击了取消”);returnfalse;}

    2025年7月27日
    4
  • 股票历史数据库(腾讯股票历史数据接口)

    歪枣网财经数据下载接口集合,百度搜索歪枣网官网序号 名称 接口描述 数据字段 更新日期 操作0 A股列表 沪深京A股基本信息 code股票代码name股票名称stype股票类型,1:深证股票,2:上证股票,3:北证股票,4:港股hsgt沪深港通,1:沪股通:2:深股通、3:港股通(沪)、4:港股通(深)、5:港股通(沪+深)bk所属板块,个股包括主板、创业板、科创板cfg成分股,该板块的成分股roeROEzgb总股本(股)ltgb流通股本(股)ltsz流通市值(元)

    2022年4月10日
    288
  • 万能密码大全[通俗易懂]

    万能密码大全[通俗易懂]aspaspx万能密码1:”or”a”=”a2: ‘)or(‘a’=’a3:or1=1–4:’or1=1–5:a’or’1=1–6:”or1=1–7:’or’a’=’a8:”or”=”a’=’a9:’or”=’10:’or’=’or’11:1

    2022年6月15日
    162

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号