谈谈我对 MVP 的理解

简介: 说实话,MVP 这种模式或者说设计思想也已经出来很久了,现在最新的使用的是 MVVM 设计模式,不断对于萌新来说,还是需要一步一步的向前走。毕竟,人不能一口吃成一个大胖子是吧。

说实话,MVP 这种模式或者说设计思想也已经出来很久了,现在最新的使用的是 MVVM 设计模式,不断对于萌新来说,还是需要一步一步的向前走。毕竟,人不能一口吃成一个大胖子是吧。

本人大四,目前正在实习,其实接触研究 MVP 也有很久了,期间找过网上的老哥们帮忙写过 MVPDemo,也从 GitHub 上看过很多项目源码。也看过他们的设计思想等。不过一直对于 MVP 中的实现引用啊,数据怎么传递过去的很迷。也看过 Google 的官方 Demo,看完后感觉还不如其他的简单 Demo 容易明白。所以我这次结合自己写的小 Demo 和理解,完整的讲述下我眼中的 MVP。希望能帮到大家,如果有什么错误还希望各位大佬多多指教。

文末附有源码

img_99e332bcdc0abe080e8358040951db56.png
01.png

话不多说,这个图是我盗来的,,,

其实对于 MVP 的好处我也不多和大家说了,说来说去也就那么几点:

1,松耦合

2,方便单元测试

3,代码整洁,容易修改

4,等等。。

先给大家展示下我的项目目录结构:


img_b2383f3dbf69fd5db8419a6708559f71.png
02.png

ApiService 目录:里面放了请求接口的方法

package com.example.root.mvp_demo.ApiInterface;

import com.example.root.mvp_demo.bean.ArticleData;

import io.reactivex.Observable;
import retrofit2.http.GET;
import retrofit2.http.Path;

/**
 * author:Jiwenjie
 * email:Jiwenjie97@gmail.com
 * time:2018/10/21
 * desc: 有关 Retrofit 和 RxJava 结合的功能接口
 * version:1.0
 */
public interface ApiService {
    @GET("/article/list/{page}/json")
    Observable<ArticleData> getArticle(@Path("page") int page);
}

这里大家应该都可以理解,因为我请求网络使用的是 RxJava + Retrofit,所以这里的开头是 Observable,如果只是单纯的 Retrofit 的话那就改成 Call 就可以了,其他不必变化。

tips:这里有一个注意点就是 @Path 和 @Query 的区别。前者是在 URL 中间添加参数,上面代码有表示,需要用 @Path 代替的参数在链接中使用 { } 包裹起来,注意名称需要对应; 而后者是属于拼接参数,即 URL 输入完成后,在最后通过 ?和 & 连接起来的参数。相信我说的很明白了,不能明白的话就去看下你们经常写的 URL 格式你也就会明白。

base 目录:这里存放了 BaseView 和 BasePresenter,这是大家的习惯啦,我正常会把 BaseActivity,BaseFragment 等都放在 Base 目录下

package com.example.root.mvp_demo.base;

/**
 * author:Jiwenjie
 * email:Jiwenjie97@gmail.com
 * time:2018/10/21
 * desc:
 * version:1.0
 */
public interface BasePresenter {
    void start();
    void destroy();
}

BaseView

package com.example.root.mvp_demo.base;

/**
 * author:Jiwenjie
 * email:Jiwenjie97@gmail.com
 * time:2018/10/21
 * desc:
 * version:1.0
 */
public interface BaseView {

}

我这里给 BaseView 定义了空实现,给 Presenter 定义了 onStart 和 onDestroy 方法。这些不用多说,相信大家都能够看懂。

bean 目录:存放实体类

contact 目录:契约目录,这里存放契约类,一般 APP 的一个界面就是一个契约类,这也是 Google 的推荐写法

package com.example.root.mvp_demo.contact;

import com.example.root.mvp_demo.base.BasePresenter;
import com.example.root.mvp_demo.base.BaseView;
import com.example.root.mvp_demo.bean.ArticleData;

/**
 * author:Jiwenjie
 * email:Jiwenjie97@gmail.com
 * time:2018/10/21
 * desc:首页文章的契约类
 * version:1.0
 */
public class ArtContact {

    public interface ArtView extends BaseView {
        void setData(ArticleData data);
    }

    public interface ArtPresenter extends BasePresenter {
        void requestData();
    }

}

顾名思义,就像签订契约,哪一个 View 和 哪一个 Presenter 相对应起来。注意 View 很重要,它是纽带。在具体使用的时候就需要 Activity 或者 Fragment 实现该 View,然后把参数传递给 Presenter。在Presenter 中做操作。

utils 目录:再看 Model 目录前先看 utils 目录,这里封装了网络请求方法

package com.example.root.mvp_demo.utils;

import com.example.root.mvp_demo.ApiInterface.ApiService;

import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.CallAdapter;
import retrofit2.Converter;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * author:Jiwenjie
 * email:Jiwenjie97@gmail.com
 * time:2018/10/21
 * desc: 有关网络请求的 Util
 * version:1.0
 */
public class RetrofitManager {

    private static ApiService apiService;

    private static OkHttpClient okHttpClient;
    private static Converter.Factory gsonConverterFactory = GsonConverterFactory.create();
    private static CallAdapter.Factory rxJaveCallAdapterFactory = RxJava2CallAdapterFactory.create();
    private static HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();

    private static OkHttpClient getClient() {
        // 可以设置请求过滤的水平,body,basic,headers

        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        return okHttpClient = new OkHttpClient.Builder()
                .addInterceptor(interceptor)    // 日志,所有请求响应都看到
                .connectTimeout(60L, TimeUnit.SECONDS)
                .readTimeout(60L, TimeUnit.SECONDS)
                .writeTimeout(60L, TimeUnit.SECONDS)
                .build();
    }

    public static ApiService getApiService() {
        if (apiService == null) {
            Retrofit retrofit = new Retrofit.Builder()
                    .client(getClient())
                    .baseUrl("http://www.wanandroid.com/")
                    .addConverterFactory(gsonConverterFactory)
                    .addCallAdapterFactory(rxJaveCallAdapterFactory)
                    .build();

            apiService = retrofit.create(ApiService.class);
        }
        return apiService;
    }
}

相信对于 Retrofit 有些了解的童鞋都知道其实 Retrofit 的实现就是 OkHttp 外面包裹了一层能够和 RxJava 连接的部分而已,所以这里的 Client 使用的是 okHttpClient。接口使用的是鸿洋大神的 玩 Android 接口。话说鸿洋老哥去头条工作了,,,大写的羡慕。都知道头条待遇好要求高。

大家可以根据我写的在做扩展。PS:其实这里有关获取 APiService 就可以修改扩展,使用 泛型和 Class 来处理代替。这样在 Model 调用的时候直接传入参数就可以。如果照我这样写,每个接口都要实现一个方法太多重复了。

Model 目录:存放 Model 用来具体操作数据的部分

package com.example.root.mvp_demo.model;

import com.example.root.mvp_demo.bean.ArticleData;
import com.example.root.mvp_demo.utils.RetrofitManager;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;

/**
 * author:Jiwenjie
 * email:Jiwenjie97@gmail.com
 * time:2018/10/21
 * desc: MVP 中的 model
 * version:1.0
 */
public class ArticleModel {

    // 获取首页技术文章的 model
    public Observable<ArticleData> getArtData(int page) {
        return RetrofitManager.getApiService().getArticle(page)
                .subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
    }

}

其实不论在 MVP 或者 MVC 模式中,Model 都是在操作数据的部分,这一点万年不变。MVVM 我还没看,不太了解,只知道好像是增加了一个 ViewModel,所以应该也是一样。

这上面从 22 行到 24 行代码是 RxJava 的功能体现,链式调用,可以说是牛逼了。他的意思是被观察者获取数据请求网络的时候在 IO 线程,所以被观察者解绑的时候也在 IO 线程。而观察者运行在 UI 线程。这里就体现了线程一行代码切换的强大。随便切,且不过来算我输,,,

如果对于这些不了解的童鞋可以去网上搜一搜,

这篇文章写的很好。
Presenter 目录:存放具体的 Prsenter 实现

大家都知道在 Activity 或者 Fragment 中需要持有 Presenter 的引用,在 Presenter 中对数据和 View 进行操作,所以我们还需要定义一个类来实现 Presenter 接口。

package com.example.root.mvp_demo.presenter;

import android.content.Context;
import android.util.Log;
import android.widget.Toast;

import com.example.root.mvp_demo.model.ArticleModel;
import com.example.root.mvp_demo.bean.ArticleData;
import com.example.root.mvp_demo.contact.ArtContact;

import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;

/**
 * author:Jiwenjie
 * email:Jiwenjie97@gmail.com
 * time:2018/10/21
 * desc: 实际的 Presenter,在这里需要持有 View 和 Model 的引用,
 * 通过 model 获取数据,把获取的数据显示在 Model 中
 * version:1.0
 */
public class Presenter implements ArtContact.ArtPresenter {

    private Context mContext;
    private ArticleModel mModel;
    private ArtContact.ArtView mView;

    // 防止内存泄漏部分
    protected Disposable disposable;

    // 初始化部分
    public Presenter(Context context, ArtContact.ArtView view) {
        this.mContext = context;
        this.mView = view;
        mModel = new ArticleModel();
    }

    @Override
    public void requestData() {
        destroy();
        disposable = mModel.getArtData(0)
                .subscribe(new Consumer<ArticleData>() {
                    @Override
                    public void accept(ArticleData articleData) throws Exception {
                        Toast.makeText(mContext, "数据获取成功", Toast.LENGTH_SHORT).show();
                        mView.setData(articleData);
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        Toast.makeText(mContext, "数据获取失败", Toast.LENGTH_SHORT).show();
                    }
                });
    }

    @Override
    public void start() {
        Log.i("Presenter", "开始获取数据");
    }

    @Override
    public void destroy() {
        if (disposable != null && !disposable.isDisposed()) {
            disposable.dispose();
        }
    }
}

这里我都写了注释,相信大家都能看懂,如果不明白的话可以私信或者 Google,如果实在不行的话,,,百度也行。注意这里我传递参数是 0,固定写死的,大家实际使用的时候需要修改一下,改成变量的形式就 OK 了。

MainActivity

重点来了,重点来了,前面说的再多都是铺垫,还要具体使用才行。先看布局,没啥好说,一个 TextView 和一个 Button。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textSize="20dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="开始"
        android:textSize="18dp"
        app:layout_constraintTop_toBottomOf="@id/tv_content" />

</android.support.constraint.ConstraintLayout>

这里给大家推荐下约束布局,真的很好用,而且上网搜一搜就能会。真的是嵌套好几层的页面他就一层搞定。对于优化性能有要求的话强烈推荐。不过没要求的话也就算了,因为写起来还有有些费事的,起码 Id 你就要想好多不重复的才行。

MainActivity 源码

package com.example.root.mvp_demo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.example.root.mvp_demo.bean.ArticleData;
import com.example.root.mvp_demo.contact.ArtContact;
import com.example.root.mvp_demo.presenter.Presenter;

public class MainActivity extends AppCompatActivity implements ArtContact.ArtView {

    private Button btn_start;
    private TextView tv_content;

    private Presenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();

        initEvent();
    }

    private void initView() {
        btn_start = findViewById(R.id.btn_start);
        tv_content = findViewById(R.id.tv_content);
        presenter = new Presenter(getApplicationContext(), this);
    }

    private void initEvent() {
        btn_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                presenter.start();
                presenter.requestData();
            }
        });
    }

    @Override
    public void setData(ArticleData data) {
        tv_content.setText(data.getData().getDatas().toString());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        presenter.destroy();
    }
}

实现了契约类中 View 的接口,重写了方法,把持了 Presenter 的引用。初始化 Presenter 的时候把 View

作为参数传递了过去。大致流程就是这样,也许真的是会了不难吧。我一开始总是不明白,现在怎么想都明白。。。希望这篇文章能帮到大家:

源码敬上:https://github.com/jiwenjie/MVP_Demo

目录
相关文章
|
运维 架构师
架构师“三部曲”——阿里云 MVP 沈剑
沈剑,公众号“架构师之路”的作者,曾任百度高级工程师和58同城高级架构师、技术委员会主席、技术学院优秀讲师,现为到家集团技术委员会主席和技术VP,同时也是快狗打车(原58速运)的CTO。本文是沈剑老师在阿里云的直播中分享的一些自己关于架构师的看法和成为架构师的心路历程的第二部分。
3474 0
架构师“三部曲”——阿里云 MVP 沈剑
|
设计模式 架构师 大数据
我的架构师之路——阿里云 MVP 沈剑
沈剑,公众号“架构师之路”的作者,曾任百度高级工程师和58同城高级架构师、技术委员会主席、技术学院优秀讲师,现为到家集团技术委员会主席和技术VP,同时也是快狗打车(原58速运)的CTO。本文是沈剑老师在阿里云的直播中分享的一些自己关于架构师的看法和成为架构师的心路历程的第一部分。
6051 0
我的架构师之路——阿里云 MVP 沈剑
|
7月前
|
设计模式 前端开发 Android开发
Android应用开发中的MVP架构模式解析
【5月更文挑战第25天】本文深入探讨了在Android应用开发中广泛采用的一种设计模式——Model-View-Presenter (MVP)。文章首先概述了MVP架构的基本概念和组件,接着分析了它与传统MVC模式的区别,并详细阐述了如何在实际开发中实现MVP架构。最后,通过一个具体案例,展示了MVP架构如何提高代码的可维护性和可测试性,以及它给开发者带来的其他潜在好处。
|
7月前
|
存储 前端开发 Java
Android应用开发中的MVP架构模式实践
【5月更文挑战第5天】随着移动应用开发的复杂性增加,传统的MVC(Model-View-Controller)架构在应对大型项目时显得笨重且不灵活。本文将探讨一种更适应现代Android应用开发的架构模式——MVP(Model-View-Presenter),并展示如何在Android项目中实现该模式以提升代码的可维护性和可测试性。通过对比分析MVP与传统MVC的差异,以及提供一个实际案例,读者将能深入了解MVP的优势和实施步骤。
100 9
|
7月前
|
前端开发 测试技术 数据处理
安卓开发中的MVP架构模式深度解析
【4月更文挑战第30天】在移动应用开发领域,模型-视图-呈现器(Model-View-Presenter, MVP)是一种广泛采用的架构模式。它旨在通过解耦组件间的直接交互来提高代码的可维护性和可测试性。本文将深入探讨MVP在安卓开发中的应用,揭示其如何促进代码的模块化,提升用户界面的响应性,并简化单元测试过程。我们将从理论概念出发,逐步过渡到实践案例,为读者提供一套行之有效的MVP实施策略。
|
设计模式 监控 自动驾驶
Android架构演进 · 设计模式· 为什么建议你一定要学透设计模式?
Android架构演进 · 设计模式· 为什么建议你一定要学透设计模式?
300 0
Android架构演进 · 设计模式· 为什么建议你一定要学透设计模式?
|
设计模式 运维 分布式计算
《架构师修炼之道》第七章--架构模式
端口适配器模式可以确保核心业务逻辑不变,在多种环境下使用,以及在隔离其他组件(负责提供数据和事件的)的状态下进行测试
291 0
|
JSON 数据处理 Android开发
带你封装MVP架构(下)|青训营笔记(一)
在 Base 类中,我们需要做的就是把每个 Activity 或者 Fragment 等这些组件,或者对应的 MVP 层会用到的基本操作以及联系都编写好。
|
前端开发 API
带你封装MVP架构(上)|青训营笔记(二)
我们做一个 MVP 架构的封装,主要其相对于MVC更加解耦,能让开发人员在编写代码的时候更加高效和舒服。
|
前端开发 Android开发
带你封装MVP架构(上)|青训营笔记(一)
我们做一个 MVP 架构的封装,主要其相对于MVC更加解耦,能让开发人员在编写代码的时候更加高效和舒服。