Android应用与MVP模式

一、什么是MVP?
在做大型软件项目的时候,架构师通常会慎重考虑使用某一个架构,这是至关重要的。在大型项目中,随着代码量越来越庞大,模块越来越错综复杂,如果没有一个好的架构,整个项目就像是一张蜘蛛网,没有人可以读懂,维护起来就非常困难,对于需求的变更与扩展就难于登天,甚至是毁灭性的。通过一个好的软件架构设计,可以使程序模块化,做到模块内部的高聚合和模块之间的低耦合。高内聚就使得程序在开发的过程中,开发人员只需要专注于一点,提高程序开发的效率。低耦合使得阅读与维护更加容易,也易于进行后续的测试以及定位问题,这就需要从整个项目的横向和纵向两个维度来划分层次。横向维度就是从整个软件层面上划分为多个层次,各个层次仅执行自己的职责;纵向维度是指按照业务模块来分隔,这样整个项目的层次结构就一目了然。MVP模式就是从横向维度上划分为三层,即Model层、View层和Presenter层。
这里写图片描述
View层:也就是视图层。View层的职责很单一,负责界面的展示,以及用户和界面的交互操作。
Model层:也就是数据层。Model层的职责也很简单,建立数据模型以及数据的读取操作。例如我们最常用的从数据库或网络上请求数据,把请求到的数据转换为数据模型,以及将数据保存至本地或者网络上。
Presenter层:它的职责比较重,负责业务逻辑的处理,它也是连接View层与Model层的桥梁。在MVP架构中,Model与View无法直接进行交互,Presenter就起到至关重要的作用,Presenter层从Model层拿到需要的数据后,进行一系列的逻辑处理,最后交给View层展示给用户。这样View与Model完全隔离,不存在耦合,它们就可以自由变化,不会影响到业务逻辑,同时把业务逻辑从View中抽离出来,使得对业务逻辑的单元测试更加容易实现,也更方便快捷。

二、MVP的五要素
在MVP模式中,通常包括五大要素:
1、View, 负责UI展示,与用户交互。
2、View interface, View实现的接口,Presenter通过此接口与View交互,依赖于接口编程,降低耦合。
3、Model,负责数据建模,检索及存储数据。
4、Model interface, Model实现的接口,Presenter通过此接口与Model交互,降低耦合。
5、Presenter,负责逻辑处理,是View与Model的交互桥梁。

三、MVP与MVC的差异
MVP是从MVC架构模式衍生出来的一种模式。MVC将系统分解为模型、视图、控制器三层,每一层都相对独立,职责单一,在实现过程中可以专注于自身的核心逻辑。
这里写图片描述
从上图可以看出:
1、MVP模式中,View与Model隔离,不能直接交互;在MVC模式中,View与Model直接交互,Model改变通知View更新。
2、在MVP模式中,View与Presenter通常是一对一的,对于复杂的View可以绑定多个Presenter来处理逻辑。在MVC模式中,一个Controller可以被多个View共享,Controller负责决定显示哪一个View.
MVP模式相比MVC模式有以下几个优点:
1、Model与View完全分离,可以修改视图而不影响模型。
2、一个Presenter可以用于多个View,而不需要改变Presenter逻辑,这样就可以在完全不改变Prsenter的情况下替换View。
3、Presenter与View的交互是通过接口来进行的,也就是说业务逻辑与UI解耦了,那么我们就可以很方便地对业务逻辑进行单元测试。

四、Android应用为什么使用MVP模式?
在Android应用中,Activity并不是一个标准的MVC模式中的Controller,它的职责是加载应用的布局和初始化用户界面,并接受并处理来自用户的操作请求,进而作出响应。随着界面及其逻辑的复杂度不断提升,Activity类的职责不断增加,以致变得庞大臃肿,这就给调试,测试和维护都带来了困难。开发软件项目,单元测试是非常重要的一环,但Android开发中对于逻辑代码的单元测试非常不方便,它与Activity的UI展示掺杂在一起,完全依赖于真机或模拟器上的展示效果,效率非常低下。如果使用MVP模式,这些问题就迎刃而解了。

五、Android中MVP模式的例子
我们来学习一下google官方给出的mvp示例项目。项目地址:https://github.com/googlesamples/android-architecture
提供了六个基于MVP架构的小项目:
todo-mvp: 基础的MVP架构。
todo-mvp-loaders:基于MVP架构的实现,在获取数据的部分采用了loaders架构。
todo-mvp-databinding: 基于MVP架构的实现,采用了数据绑定组件。
todo-mvp-clean: 基于MVP架构的clean架构的实现。
todo-mvp-dagger2: 基于MVP架构,采用了依赖注入dagger2。
dev-todo-mvp-contentproviders: 基于mvp-loaders架构,使用了ContenPproviders。
我们来学习一个最简单的todo-mvp项目。
这个项目实现的功能是备忘录。首先看一下项目的整体结构,主要是按照功能模块来划分目录结构,主目录下有BaseView和BasePresenter两个接口,还有六个包,分别是任务的添加编辑(addedittask),任务完成情况的统计(statistics),任务的详情(taskdetail),任务列表的显示(tasks),数据存取(data)以及工具类(util)。先看一下两个基类接口BasePresenter和BaseView

public interface BasePresenter {
    void start();
}

它是Presenter的基类,start方法的作用是从Model中获取数据,最终调用View接口显示。

public interface BaseView<T> {
    void setPresenter(T presenter);
}

它是所有View的基类,setPresenter方法用于给View设置Presenter。
Model层的实现:
Model层在data包目录下,主要是对数据的存取操作。

public interface TasksDataSource {

    interface LoadTasksCallback {

        void onTasksLoaded(List<Task> tasks);

        void onDataNotAvailable();
    }

    interface GetTaskCallback {

        void onTaskLoaded(Task task);

        void onDataNotAvailable();
    }

    void getTasks(@NonNull LoadTasksCallback callback);

    void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);

    void saveTask(@NonNull Task task);

}

这是Model接口,Presenter通过此接口存取数据。LoadTasksCallback和GetTaskCallback定义了数据操作的回调接口,他们在Presenter里实现,当数据操作成功或失败后用于通知Presenter.

public class TasksLocalDataSource implements TasksDataSource {

    @Override
    public void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback) {

        //获取taskId对应的task
        if (task != null) {
            callback.onTaskLoaded(task);
        } else {
            callback.onDataNotAvailable();
        }
    }

    @Override
    public void saveTask(@NonNull Task task) {
        checkNotNull(task);
        SQLiteDatabase db = mDbHelper.getWritableDatabase();

        ContentValues values = new ContentValues();
        values.put(TaskEntry.COLUMN_NAME_ENTRY_ID, task.getId());
        values.put(TaskEntry.COLUMN_NAME_TITLE, task.getTitle());
        values.put(TaskEntry.COLUMN_NAME_DESCRIPTION, task.getDescription());
        values.put(TaskEntry.COLUMN_NAME_COMPLETED, task.isCompleted());
        db.insert(TaskEntry.TABLE_NAME, null, values);
        db.close();
    }
}

TasksLocalDataSource是Model的具体实现。
Presenter层的实现:
首先给出一个契约类,定义了Presenter和View所有的接口,可以很清晰看到Presenter和View的所有操作,便于维护和管理。

public interface AddEditTaskContract {

    interface View extends BaseView<Presenter> {

        void showEmptyTaskError();

        void showTasksList();

        void setTitle(String title);

        void setDescription(String description);

        boolean isActive();
    }

    interface Presenter extends BasePresenter {

        void createTask(String title, String description);

        void updateTask( String title, String description);

        void populateTask();
    }
}

我们再看一下具体的Presenter类AddEditTaskPresenter

public class AddEditTaskPresenter implements AddEditTaskContract.Presenter,
        TasksDataSource.GetTaskCallback {

    public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
            @NonNull AddEditTaskContract.View addTaskView) {
        mTaskId = taskId;
        mTasksRepository = checkNotNull(tasksRepository);
        mAddTaskView = checkNotNull(addTaskView);

        mAddTaskView.setPresenter(this);
    }

    @Override
    public void start() {
        if (mTaskId != null) {
            populateTask();
        }
    }

    @Override
    public void populateTask() {
        if (mTaskId == null) {
            throw new RuntimeException("populateTask() was called but task is new.");
        }
        mTasksRepository.getTask(mTaskId, this);
    }

    @Override
    public void onTaskLoaded(Task task) {
        // The view may not be able to handle UI updates anymore
        if (mAddTaskView.isActive()) {
            mAddTaskView.setTitle(task.getTitle());
            mAddTaskView.setDescription(task.getDescription());
        }
    }
}

首先将此Presenter设置给View,然后调用Model层的接口getTask方法,等待Model层执行回调函数onTaskLoaded,在回调函数里调用View层接口更新View。
View层的实现:
View是在Fragment中实现的,Activity作为Context,完成对Fragment的add和remove。

public class AddEditTaskFragment extends Fragment implements AddEditTaskContract.View {

    @Override
    public void onResume() {
        super.onResume();
        mPresenter.start();
    }

    @Override
    public void setPresenter(@NonNull AddEditTaskContract.Presenter presenter) {
        mPresenter = checkNotNull(presenter);
    }

    @Override
    public void setTitle(String title) {
        mTitle.setText(title);
    }
}

View很简单,就是更新UI,接收事件调用Presenter处理业务逻辑。

六、MVP单元测试
MVP各层的单元测试选型
这里写图片描述
P层:不需要任何Android环境,因此使用Junit测试即可。
V层:使用Google强大的Espresso进行UI的测试。测试过程中需要真机或模拟器,并做真实的操作。
M层:涉及到数据库相关操作,因此需要依赖Android环境,使用AndroidJUnitRunner进行测试
可以参考android官方MVP单元测试的用例。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值