MVP 架构实战深入浅出

本文为菜鸟窝作者 吴威龙 连载

菜鸟窝是专业的程序猿在线学习平台,提供最系统的 Android 项目实战课程

如需转载,请联系菜鸟窝公众号(cniao5),并注明出处。

前言

上一篇文章
RecycleView 综合使用案例(结合 ButterKnife、Retrofit、Picasso) 分享了在使使用 RecycleView 的时候如何结合 ButterKnife、Retrofit、Picasso 等框架进行使用。现在来聊聊 MVC 以及 MVP 框架吧。

下面来看看 MVC 的介绍:

MVC简介

MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

其中 M 层处理数据,业务逻辑等;V 层处理界面的显示结果;C 层起到桥梁的作用,来控制 V 层和 M 层通信以此来达到分离视图显示和业务逻辑层。

Android 中的 MVC

  • 视图层(View)

一般采用 XML 文件进行界面的描述,这些 XML 可以理解为 AndroidApp 的 View。使用的时候可以非常方便的引入。同时便于后期界面的修改。逻辑中与界面对应的 id 不变化则代码不用修改,大大增强了代码的可维护性。

  • 控制层(Controller)

Android 的控制层的重任通常落在了众多的 Activity 的肩上。这句话也就暗含了不要在 Activity 中写代码,要通过 Activity 交割 Model 业务逻辑层处理,这样做的另外一个原因是 Android 中的 Activity 的响应时间是 5s,如果耗时的操作放在这里,程序就很容易被回收掉。

  • 模型层(Model)

我们针对业务模型,建立的数据结构和相关的类,就可以理解为 AndroidApp 的 Model,Model 是与 View 无关,而与业务相关的。对数据库的操作、对网络等的操作都应该在 Model 里面处理,当然对业务计算等操作也是必须放在的该层的。就是应用程序中二进制的数据。

MVC MVP 对比

image

通过分析上图,只需知道 MVC 传统模式是没有把 View 和 Model 层隔离开的,MVP 模式则是 View 层和 Model 完全解耦开,通过 Presenter 这个中间人进行传递信息。

下面看看 MVP 的介绍:

MVP

image

View:负责绘制 UI 元素、与用户进行交互(在 Android 中体现为 Activity)

Model:负责存储、检索、操纵数据(有时也实现一个 Model interface 用来降低耦合)

Presenter:作为 View 与 Model 交互的中间纽带,处理与用户交互的负责逻辑。

View interface:需要 View 实现的接口,View 通过 View interface 与 Presenter 进行交互,降低耦合,方便进行单元测试

一句话解释就是:Presenter 是 View 和 Model 之间的代理。

MVP 优点

  1. 降低耦合度,实现了 Model 和 View 真正的完全分离,可以修改 View 而不影响 Modle
  2. 模块职责划分明显,层次清晰
  3. 隐藏数据
  4. Presenter 可以复用
  5. 利于测试驱动开发
  6. View 可以进行组件化
  7. 代码灵活性

MVP缺点

  1. Presenter 中除了应用逻辑以外,还有大量的 View->Model,Model->View 的手动同步逻辑,造成 Presenter 比较笨重,维护起来会比较困难。

  2. 由于对视图的渲染放在了 Presenter 中,所以视图和 Presenter 的交互会过于频繁。

  3. 如果 Presenter 过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么 Presenter 也需要变更了。

  4. 额外的代码复杂度及学习成本。

代码实现:

MVP 是一种思想,每个人的理解不一样,所以每个人的实现都是大同小异的。谷歌推出官方的 MVP Demo,我们可以参考谷歌给出的进行稍微修改一点点,形成自己风格的 mvp 模式。注意,模式的实现是没有所谓的标准的,只要达到这种解耦效果就可以了。

这里以 菜鸟手机助手 的【推荐】栏目举例说明

模块解析:

  • Contract 接口:里面定义 presenter 接口 和 view 接口

  • presenter :负责和 view,module 交互

  • view :基本都是对控件进行更新即可

BaseView

public interface BaseView {

    //声明公共的一些方法
    void showLodading();//显示加载进度条
    void dimissLoading();//关闭加载进度条
}

RecommendContract

该类存放两个接口,View 接口和 Presenter 接口。
以前两个接口都是分开写的,现在合起来放在一个接口类里面,显得不那么凌乱了。

接口 View 给具体视图层实现,譬如本例中的 RecommendFragment。

接口 Presenter 给具体的 Presenter 层实现,譬如本例的 RecommendPresenter。


public interface RecommendContract {

    //接口与接口之间的继承是用 extends
    interface View extends BaseView{

        void showResult(List<AppInfo> datas);  //显示数据
        void showNodata();                     //提示没数据
        void showError(String msg);            //提示错误
    }
    
    // BasePresenter 暂时为空,就不列代码出来了,以后可以增加
    interface  Presenter extends BasePresenter{

        public void requestDatas();//请求数据
    }

}

Model 层

RecommendModel 类里面调用到的 HttpManager、ApiService 类的代码就不贴出来了,因为本例主要讲 MVP 模式。

public class RecommendModel {

    // presenter 层调用该方法,执行完毕有回调方法
    public  void getApps(Callback<PageBean<AppInfo>> callback){

        HttpManager manager = new HttpManager();

        ApiService apiService =manager.getRetrofit(manager.getOkHttpClient()).create(ApiService.class);

        apiService.getApps("{'page':0}").enqueue(callback);
    }
}

Presenter 层

实现 Contract 层接口 RecommendContract.Presenter,实现该接口下的抽象方法。

实现抽象方法:requestDatas(),请求数据

代码中 引用 model 层的对象RecommendModel mModel,
通过 mModel.getApps() 调用 Model 层的具体方法实现需求。

引用 view 接口实例对象,接口引用指向一个对象 RecommendContract.View mView,
通过 mView.showLodading()、mView.showNodata() 等调用 View 层具体方法实现需求。

public class RecommendPresenter implements RecommendContract.Presenter {

    //引用 model 层的对象
    private RecommendModel mModel;

    //引用 view 接口实例对象,接口引用指向一个对象
    private RecommendContract.View mView;

    //构造方法中传过来 view 对象
    public RecommendPresenter(RecommendContract.View view){

        this.mView = view;

        mModel = new RecommendModel();
    }


    //实现 RecommendContract.Presenter 接口,重写接口的抽象方法
    @Override
    public void requestDatas() {

        //调用 实现了RecommendContract.View 接口的  fragment 里面重写的 showLodading()方法
        mView.showLodading();

        mModel.getApps(new Callback<PageBean<AppInfo>>() {
            @Override
            public void onResponse(Call<PageBean<AppInfo>> call, Response<PageBean<AppInfo>> response) {

                if(response !=null){

                    mView.showResult(response.body().getDatas());
                }
                else{
                    mView.showNodata();
                }

                mView.dimissLoading();
            }
            @Override
            public void onFailure(Call<PageBean<AppInfo>> call, Throwable t) {
                mView.dimissLoading();
                mView.showError(t.getMessage());
            }
        });
    }
}

View 层

实现 Contract 层接口 RecommendContract.View,实现该接口下的几个抽象方法:
showLodading(),
dimissLoading(), showNodata(), showError(), showResult()

代码中使用 RecommendContract.Presenter mPresenter 接口实例对象,接口引用指向一个对象,
通过 mPresenter.requestDatas() 调用 Presenter 的 requestDatas()方法。
以此达到解耦的目的。

public class RecommendFragment extends Fragment  implements RecommendContract.View {

    @BindView(R.id.recycle_view)
    RecyclerView mRecyclerView;

    private RecomendAppAdatper mAdatper;

    private ProgressDialog mProgressDialog;

    private RecommendContract.Presenter mPresenter;//presenter接口实例对象,接口引用指向一个对象

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {


        View view = inflater.inflate(R.layout.fragment_recomend, container, false);
        ButterKnife.bind(this, view);

        mProgressDialog = new ProgressDialog(getActivity());

        //实例化 Presenter 
        mPresenter = new RecommendPresenter(this);

        initData();
        return view;

    }
    private void  initData(){

        //调用 presenter 去请求数据,实际上,presenter 是指挥 Model 去做实际操作
        mPresenter.requestDatas();
    }

    //数据显示
    private void initRecycleView(List<AppInfo> datas){

        //为RecyclerView设置布局管理器
        mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

        //为RecyclerView设置分割线(这个可以对DividerItemDecoration进行修改,自定义)
        mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.HORIZONTAL_LIST));

        //动画
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        mAdatper = new RecomendAppAdatper(getActivity(),datas);

        mRecyclerView.setAdapter(mAdatper);
    }

    // 实现 RecommendContract.View  接口后  重写的抽象方法
    @Override
    public void showResult(List<AppInfo> datas) {
        initRecycleView( datas);
    }

    @Override
    public void showNodata() {

        Toast.makeText(getActivity(),"暂时无数据,请吃完饭再来",Toast.LENGTH_LONG).show();
    }
    @Override
    public void showError(String msg) {
        Toast.makeText(getActivity(),"服务器开小差了:"+msg,Toast.LENGTH_LONG).show();
    }

    @Override
    public void showLodading() {

        mProgressDialog.show();
    }

    @Override
    public void dimissLoading() {

        if(mProgressDialog.isShowing()){
            mProgressDialog.dismiss();
        }
    }
}

总结

通过上面代码分析可以很清晰的明白 MVP 框架模式是怎么进行代码解耦的。下面再次梳理一下 MVP 实现步骤:

  • 定义 Presenter、View 接口(可以向上面例子,放在 Contract 接口里面):接口里面定义 presenter 层、view 层的抽象方法。简单的说就具体实现类所要实现的方法。

  • 具体 Presenter 层实现类实现定义的 Presenter 接口,实现该接口的抽象方法。
    比如 RecommendPresenter 类实现了 RecommendContract.Presenter 接口,实现 requestDatas()

  • 具体 View 层实现类实现定义的 View 接口,实现该接口下的抽象方法。
    比如 RecommendFragment.

  • RecommendFragment 通过类中的 RecommendPresenter 对象调用 Presenter 层里面的方法:requestDatas() 方法。在 Presenter 层中通过 Model 对象调用 Model 层里面的具体方法:getApps(). Model 层的方法执行完后通过回调把结果回调给 Presenter 层,Presenter 层再通过 View 层的接口实例对象,调用相关方法把结果回调到具体实现了该 View 接口的 View 层。

撸这个项目的一半,你就是大神 , 戳http://mp.weixin.qq.com/s/ZagocTlDfxZpC2IjUSFhHg

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容