如何将原项目重构成 MVP 模式

  • 2016-10-01
  • 25,666
  • 39

最近在做公司项目的重构工作,主要重构点包括:

  1. 网络请求框架由 HttpURLConnection + Handler 改为 Retrofit + OkHttp + RxJava
  2. 事件通知由 安卓广播 改为 RxBus
  3. 图片加载框架由 ImageLoader 改为 Glide
  4. 代码优化(瘦身,内存泄漏分析等)
  5. 将传统Activity作为God Object的MVC模式 改为兼容原代码的MVP模式

这是现在Android应用的主流架构: Retrofit + OkHttp + RxJava + MVP

今天主要分享一下,如何将原项目转为MVP模式,并且兼容原有代码。有空的话再分享其他模块的重构经过。

MVP,全称 Model-View-Presenter,其中Presenter解耦了Model与View,使得每个模块的职责更加单一,Model负责获取数据,View只关心视图的绘制,Presenter关联Model和View处理业务逻辑。

与传统的MVC模式相比更加易于维护和排查漏洞,以及更容易编写单元测试。

MVP模式也有一些缺点,比如类文件数目增加,每一层都需要接口与实现类,在系统相对不是特别复杂的情况下可以舍弃Model层与Presenter接口,MVP模式并没用固定的模板,其中每一层的实现可以根据各自的业务场景做相应的调整。

并不是所有的页面都需要使用MVP模式,相对简单的页面完全可以用原来的实现模式。

推荐几篇关于MVP模式介绍比较全面的文章:

  1. Android MVP 详解(上)
  2. Android MVP 详解(下)
  3. Google 官方Android MVP架构实践

下面正式开始,如有不妥之处,请老司机多多指教。

首先定义各层的接口,各层之间通过接口耦合:

1. View接口

public interface IView{ }

2.Model接口:

public interface IModel { }

3.Presenter 接口:

public interface IPresenter<T extends IView> {
    
    void attachView(T view);
    void start();
    void detachView();  
}

实现一下Ipresenter接口中简单的逻辑:BasePresenter ,使用时直接继承该类:

public abstract class BasePresenter<T extends IView,M extends IModel> implements IPresenter<T> {

    protected static final String TAG = "BasePresenter";
    protected T mView;
    protected M mModel;

    @Override
    public void attachView(T view) {
        mView = view;
    }

    @Override
    public void detachView() {
        mView = null;
    }

    public boolean isViewAttached() {
        return mView != null;
    }

    public T getView() {
        return mView;
    }
 
    public M getModel() {
       return mModel;
    }

}

到目前为止MVP模式的基础代码已经编写完成了,下面的介绍是为了兼容原代码。

现有项目中一般都会有一个BaseActivity,为了方便在Activity中使用MVP模式,以及不影响现有页面,再次实现一个IViewActivity继承BaseActivity封装MVP相关逻辑。如果要使用MVP模式则继承IViewActivity,否则继承BaseActivity,从而兼容了原有代码,并且引入MVP模式开发新页面。

下面是IViewActivity:

/**
 * Mvp base activity
 * Created by qiulinmin on 16-9-22.
 */
public abstract class IViewActivity<P extends IPresenter> extends BaseActi implements IView {

    protected P mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter = onLoadPresenter();
        getPresenter().attachView(this);
        initEventAndData();
        if(getPresenter() != null) {
            getPresenter().start();
        }
    }

    public P getPresenter() {
        return mPresenter;
    }

    @Override
    protected void onDestroy() {
        if (getPresenter() != null) {
            getPresenter().detachView();
        }
        super.onDestroy();
    }

    protected abstract P onLoadPresenter();
    protected abstract void initViews(Bundle savedInstanceState);
    protected abstract void initEventAndData();
}

在 onLoadPresenter() 中初始化Presenter,初始化工作完成之后会调用IPresenter.start()方法转到Present层处理业务逻辑。

以上关于MVP模式所有代码都已经编写好了。

使用时为了减少类文件数目,可以写一个Contract接口来统一管理每一层的接口,例如:

public interface DemoContract {

    interface View extends IView {

    }

    interface Model extends IModel {

    }

    interface Presenter extends IPresenter<View> {

    }

}

最后实现present,model接口,

public class DemoPresenter extends BasePresenter<DemoContract.View, DemoContract.Model> implements DemoContract.presenter { }
public class DemoModel implements DemoContract.Model {  }

在DemoPresenter的构造器中需要初始化DemoModel,因为是接口耦合的,在初始化DemoModel时完全可以替换成其他的实现。

最后的最后Activity作为View层实现DemoContract.View:

public class DemoActivity extends IViewActivity<DemoContract.Presenter> implements DemoContract.View {

    protected DemoContract.Presenter onLoadPresenter() {
        return new DemoPresenter();
    }

    protected void initViews(Bundle savedInstanceState) {
        //略...
    }

    protected void initEventAndData() {
        //略...
    }

}

Activity顿时感到如释重负,它只要关心如何绘制UI,把逻辑处理的工作丢给了Presenter,同时存取数据的活交给了Model,Presenter 通过 Model 获取数据,通知 View 绘制界面。而且他们之间又是通过接口耦合的,只要替换掉实现,就能方便的改变需求,Presenter实现中可以不包含任何Android相关的代码,因为是纯Java代码,所以可以方便的使用JUnit配合Mokito进行单元测试。

附上Mvp模板代码生成工具:手牵手教你写IntelliJ Idea插件:MvpGenerator

>> 转载请注明来源:如何将原项目重构成 MVP 模式

评论

  • 王玮回复

    不错,学习了

  • 孤城回复

    东西有点多,看不懂

    • admin回复

      可以先看文章中推荐的几篇介绍MVP模式的文章,如有不懂的欢迎留言讨论。

  • hank回复

    能不能带上github地址

    • admin回复

      OK,回头我传一份到Github上

  • Jun回复

    感谢,最近正好需要。

  • 回复

    非常好,学习了

  • KingJA回复

    头像笑得有点不正经

    • pqpo回复

      他就是个不正经人

  • 回复

    楼主,你文章左右空白处,鼠标拖动时的图形变换的算法是什么?想通过安卓实现

    • Forwards回复

      很酷炫

  • gene回复

    向lz学习。

  • tinywing回复

    楼主,貌似鼠标拖动时产生的动画让浏览器不堪重负啊,浏览器占用cpu 百分之三十多,关掉楼主文章的网页就cpu就降下来了

    • tinywing回复

      文章挺不错的,最近刚入门MVP,支持楼主

    • sogood回复

      是啊,打开网页没多久,手提的风扇就呼呼作响。

    • pqpo回复

      是什么浏览器,我这边试了没什么影响,如果影响阅读的话会考虑去掉背景

  • Sean回复

    背景动画玩了半个小时…..

    • Haikilom回复

      真的有意思了

  • pqpo回复

    看大家对背景很感兴趣,是别人写好的,只要在网页内引入:canvas-nest.min.js

  • lz回复

    Presenter 中的 mModel 应该在哪初始化?

  • lz回复

    Presenter 中的 mModel 应该在哪初始化?

    • pqpo回复

      mModel 需要在Presenter的构造器中初始化,不是必须的,如果引入依赖注入框架会更方便

  • Jiaptti回复

    你好,你的BasePresenter里面有一个getModel方法,然而,你并没有方法或者接口给它赋值

    • pqpo回复

      mModel 需要在Presenter的构造器中自行初始化,Presenter定义了泛型只是为了约束接口

  • 键盘侠回复

    也就说了一下mvp模式项目的构建,看完还是不会怎么重构自己的项目

    • pqpo回复

      重构项目的主要部分其实是 IViewActivity ,分享了自己的做法。不同项目之间可能会有差异

  • Toomm回复

    看了后感觉受益匪浅,您方便把源码分享一份嘛?没找到GitHub地址…

  • kilin回复

    楼主是否可以传一份代码参阅一下,项目中正好遇到了这个需求,非常感谢

  • sin回复

    LZ,能否给一份代码让我看看可以吗 MVP的,大道理懂,怎么写还是有问题。。。

  • Jun回复

    能不能问下Presenter的start是做什么的用。这里的attachView为何就不用检查空指针的?我基本按照你的做,发现到attachView为止都还是好好地,但是VIew的方法并没有回调成功getPresenter().attachView(this); initEventAndData(); if(getPresenter() != null) { getPresenter().start(); }

    • Dorae_min回复

      attachView() 也要判空,漏了start() 方法是页面的起点,比如进入页面要要加载数据,可以在presenter的start() 方法中请求,而不需要在Activity调用presenter

  • 张伟回复

    确实不错,具体使用,博主能不能具体讲讲讲,没有用过

  • 陈承回复

    谢谢分享

  • Hn回复

    有demo代码么?

    • pqpo回复

      代码基本上都在文章中了,自己组织一下吧

回复给 lz 点击这里取消回复。