框架设计之MVP模式

 

背景

随着Java/Android技术快速的发展更新,同时为了开发更快捷、代码可读性更高、更解耦(UI和数据逻辑的解耦)一堆牛人设计了一堆的框架模式。在Android中最出名的莫过于MVC、MVP、MVVM这三种模式了。首先我们分别讲一讲这三种设计模式的原理,以及优缺点。最后我将结合公司目前的项目利用前面说过的泛型来实现一个通用版的MVP模式架构。

一、MVC模式

MVC全名是Model View Controller,如下图,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码。要知道Android开发初期就如同盘古开天一样,一片混沌啥子都没有,不像现在要车、要轮子Google一下一堆一堆看的人眼花。所以前期工程师们写代码的时候很没有章法,完全是为了堆功能,代码可读性很差(这句话只针对像我一样的在菜鸟底层打滚的人,大牛排外)。这个时候英雄就出现了,大牛说你们写的代码都是写什么玩意?我来造一个模子,你们以后就照着这个模子写代码。如是MVC模式就这样诞生了。下面就是我扣来的大牛初期造模子时的架构构思图。

如上图所示,整个架构将代码分成了三大模块,View层负责UI绘制,Model层负责提供数据。Controller层就像它的命名一样,作为整个框架的控制中心,起到桥梁作用来控制V层和M层通信以此来达到分离视图显示和业务逻辑层,在Android中这个控制中心的角色一般由Activity来扮演。Model被单独分隔出来负责业务逻辑的处理以及数据的提供,比如一些耗时任务都将交由Model层处理完成后,再返回一个反馈数据给View用来展示。

虽然通过大牛的努力完成了一个可供万人模仿的架构但是它并没有达到前面说的代码解耦的目的。View其实在整个架构中能做的事情很少。由于Activity充当了Controller的角色这就导致在Activity里面代码十分臃肿,而且看明白上面模式图的应该能看出来,MVC并没有实现View和Model的解耦。他们之间依旧有着单向的强关联,这个时候如果有个像我一样的大白菜来开发一个复杂点的功能,就有可能造成内存泄漏等问题。(什么是内存泄漏将在后面详细讲解)。其实大牛早就发现了这个框架的缺点:如果项目太大代码就会很臃肿,可读性还是很差。所以MVP模式就应运而生了。

二、MVP模式

我们先来看一看我扣来的MVP模式架构图

从上图可以看出来,MVP同样由三部分组成,View层负责UI。Model层负责提供数据,Presenter层作为View和Model层的桥梁,处理业务逻辑。那么MVP和MVC到底与什么区别呢?仔细看图可以发现,MVP的model和view之间是没有直接关联的。model和view的通信是完全通过presenter这个桥梁来完成的。这就完成了View和model的解耦。而presenter作为桥梁会持有view和model的接口实例。咦?这里又多出来个名词“接口实例”,啥叫接口实例呢?了解过设计模式的同学应该清楚,目前所有的设计模式都依赖于六大设计原则,而其中有两个原则叫做“接口隔离和依赖倒置”。意思可以理解为类与类(实例与实例)之间的依赖关系应该建立在最小的接口上,调用者和被调用者(也叫实现者)不应该直接依赖具体的实体,而是依赖两者的抽象或接口。这就是我们常听到的面向抽象编程思想。而我们这里说的接口实例就是面向抽象编程的一种体现:persenter只会持有view和model的接口变量,变量的值就是接口的具体实现者,通过这种设计方式再次实现了presenter与view、model的解耦,极大的避免了高耦合可能带来的内存泄漏等问题。说了这么多理论,可能大家还是满头what。作为程序猿还是直接上代码可能更加直接一些。这里我将为大家讲解一个公司正在使用的基于MVP模式的基础应用框架。我们都知道现在的Android很炫酷,各种分屏各种花哨UI和功能,用起来也很流畅。而这些UI又主要分成activity和Fragment两种展现方式,可能这样说也不对,因为Fragment也是依赖于activity的。但是如今开发中的activity很多时候真的只是一窗口了,具体的展示(如分屏、分tab展示等)以及业务逻辑都在各个Fragment中去实现。所以我们的框架里面同样会分成Activity和fragment。

首先来介绍构成MVP基础框架的三大模块

package com.model.singlecode.base.mvp;

/**
 * 创建时间:2019/5/14
 * 创建人:singleCode
 * 功能描述:base model
 **/

public interface IBaseModel {

}
package com.model.singlecode.base.mvp;


import android.support.annotation.NonNull;

/**
 * 创建时间:2019/5/14
 * 创建人:singleCode
 * 功能描述:base view
 **/

public interface IBaseView {
    /**
     * 初始化presenter
     * <p>
     * 此方法返回的presenter对象不可为空,由具体界面实现该方法
     */
    @NonNull
    BasePresenter initPresenter();

}
package com.model.singlecode.base.mvp;


import android.support.annotation.NonNull;

import com.model.singlecode.base.mvp.helper.RxSubHelper;


/**
 * base Presenter抽象类
 * 前面在说泛型时,我们就说过泛型在设计模式中很常用,这里我们就采用了泛型类的方式来定义MVP模式中的presenter层
 * 在讲解presenter层时,我们说presenter会持有view和model的接口实例。但是每一个界面的view和model都不一样,
 * 所以采用泛型就能够很完美的帮我们实现所有界面和业务逻辑依赖于同一个底层框架库。
 * @param <M> 指向model的泛型参数
 * @param <V> 指向view的泛型参数
 */

public abstract class BasePresenter<M, V> {
    public M mIModel;
    public V mIView;
    /**
     *  这里是我们自定义的一个观察者被观察者模式的帮助类。
     *  整个框架中我们将应用到目前比较流行的OkHTTP+retrofit+订阅模式来实现数据获取
     */
    protected RxSubHelper rxSubHelper = new RxSubHelper();

    /**
     * 返回presenter想持有的Model引用
     * 由具体的model层返回一个具体的实例对象
     *
     * @return presenter持有的Model引用
     */
    public abstract M getModel();

    /**
     * 绑定IModel和IView的引用,初始化泛型变量
     * @param m model
     * @param v view
     */
    public void attachMV(@NonNull M m, @NonNull V v) {
        this.mIModel = m;
        this.mIView = v;
        this.onStart();
    }

    /**
     * 解绑IModel和IView
     */
    public void detachMV() {
        rxSubHelper.unSubscribe();
        mIView = null;
        mIModel = null;
    }

    /**
     * 解绑所有的订阅
     */
    public void clearAllSubscribe() {
        rxSubHelper.clearSubscribe();
        mIView = null;
        mIModel = null;
    }

    /**
     * IView和IModel绑定完成立即执行
     * <p>
     * 实现类实现绑定完成后的逻辑,例如数据初始化等,界面初始化, 更新等
     */
    public abstract void onStart();

    public abstract void onRefresh();

}

 上面的三个段代码就是构成我们基础框架的核心。看起来代码量是不是很少?但就是这一点点的代码却能够帮助我们完成所有界面基于MVP模式进行开发。有人会说,你扯淡,就你这点代码做不到。哈哈,那你看完下面这段代码还会觉得我做不到吗?

package com.model.singlecode.base.mvp;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AppCompatDelegate;


import org.greenrobot.eventbus.EventBus;

import androidx.annotation.Nullable;
import butterknife.ButterKnife;
import butterknife.Unbinder;

/**
 * 创建时间:2019/5/14
 * 创建人:singleCode
 * 功能描述:Base CompatActivity对Activity进行上层分装,将一些公共的初始化操作放到一起。
 * 如:状态栏设置、布局xml文件引入、以及EventBus、ButterKnife等的注册与解注册等 
 **/
public abstract class BaseCompatActivity extends AppCompatActivity {

    protected Context mContext;
    private Unbinder mUnBinder;
    static {
        //5.0以下兼容vector
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


        //1、设置状态栏主题颜色
//        StatusBarUtils.setStatusBar(this);

        //2、引入布局,设置布局Id
        setContentView(getLayoutId());

        //3、ButterKnife.bind 绑定并初始化布局控件
        mUnBinder = ButterKnife.bind(this);


        //4、设置状态栏透明色
//        StatusBarUtils.setTransparent(this);

        //5、设置屏幕显示的方向,此处为竖屏
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

        //6、初始化数据
        initData();

        if(!EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().register(this);
        }

        //7、子类初始化数据
        initView(savedInstanceState);

        //8、将当前Acitivity加入栈
//        AppManager.getAppManager().addActivity(this);

        onLoadingView();

    }

    /**
     * 首次加载布局
     */
    protected void onLoadingView(){}

    /**
     * 获取当前layouty的布局ID,用于设置当前布局
     * 交由子类实现
     *
     * @return layout Id
     */
    protected abstract int getLayoutId();

    /**
     * 初始化view
     * <p>
     * 子类实现 控件绑定、视图初始化等内容
     *
     * @param savedInstanceState savedInstanceState
     */
    protected abstract void initView(Bundle savedInstanceState);

    /**
     * 初始化数据
     * <p>
     * 子类可以复写此方法初始化子类数据
     */
    protected void initData() {
        mContext = this;
    }


    protected void openStartActivity(Class clazz) {
        Intent intent = new Intent(this, clazz);
        startActivity(intent);
    }


    @Override
    protected void onDestroy() {
        mUnBinder.unbind();
        if(EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().unregister(this);
        }
        super.onDestroy();
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

}
package com.model.singlecode.base.mvp;


/**
 * 创建时间:2019/5/14
 * 创建人:singleCode
 * 功能描述:base mvp Activity  将MVP模式引入到Activity中来
 *,这里再次用到了泛型类的知识,将activity定义成一个泛型类,与某个presenter、model进行绑定。
 * 同时又是base activity 的子类,完成了必要的初始化工作
 **/

public abstract class BaseMVPCompatActivity<P extends BasePresenter, M extends IBaseModel> extends
        BaseCompatActivity implements IBaseView {

    /**
     * presenter 具体的presenter由子类确定
     */
    protected P mPresenter;

    /**
     * model 具体的model由子类确定
     */
    private M mIMode;

    /**
     * 初始化数据
     * <p>
     * 子类可以复写此方法初始化子类数据
     */
    protected void initData() {
        super.initData();
        mPresenter = (P) initPresenter();
        if (mPresenter != null) {
            mIMode = (M) mPresenter.getModel();
            if (mIMode != null) {
                mPresenter.attachMV(mIMode, this);
            }
        }
    }

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

BaseMVPCompatActivity通过泛型参数的方式将MVP模式引入到了Activity中,使用时我们只需要如下示例一样直接继承BaseMVPCompatActivity并绑定对应的presenter和model,实现presenter中绑定的View即可。

package com.model.singlecode.mvpdemo;

import android.support.annotation.NonNull;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

import com.model.singlecode.base.mvp.BaseMVPCompatActivity;
import com.model.singlecode.base.mvp.BasePresenter;
import com.model.singlecode.basemvp.R;
import com.model.singlecode.mvpdemo.mvp.contract.MainContract;
import com.model.singlecode.mvpdemo.mvp.presenter.MainPresenter;

import java.util.List;

import butterknife.BindView;

public class MainActivity extends BaseMVPCompatActivity<MainContract.MainPresenter, MainContract.MainModel>
        implements MainContract.MainView {
    @BindView(R.id.tx_main)//通过butterknife绑定布局文件中的控件,省去繁琐的findViewById
    TextView tx_main;

    @Override
    protected int getLayoutId() {//绑定布局文件
        return R.layout.activity_main;
    }

    @Override
    protected void initView(Bundle savedInstanceState) {
        tx_main.setText("mvp demo");
        tx_main.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });
        mPresenter.LoadData();//activity只需要调用presenter方法获取数据即可
    }

    @NonNull
    @Override
    public BasePresenter initPresenter() {//返回绑定的具体presenter 实例
        return new MainPresenter();
    }

    @Override
    public void onLoadData(List<String> list) {
        //调用presenter方法获取的结果返回数据
    }
}

相信大部分童鞋对contract有点疑惑,这个分组是干啥用的呢?同时View模块去哪了?contract的作用是作为契约,目的是将presenter、views、model等接口集中关联起来,便于统一管理。而View模块就是各个Activity或者Fragment实例,由他们实现各View接口。有人说你这样写和上面框架的图好像有点不一样。兄得,代码千千万,别那么死板。我们要学习设计思想而不是死板得代码本身。虽然这里多出来了一个Contract模块,但并没有影响整个框架的结构,反而多了一个接口集中管理的地方,使我们的代码不那么松散。这样去看我们的代码结构是不是清晰很多?每个界面或者功能模块对应一条mvp线。而activity的任务就是通过presenter调用方法获取结果。

在Fragment中引入MVP框架的原理和使用方式与上面的Activity类似,这里只贴出关键代码就不再重复说明了。

package com.model.singlecode.base.mvp;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;


import org.greenrobot.eventbus.EventBus;

import androidx.annotation.LayoutRes;
import androidx.annotation.Nullable;
import butterknife.ButterKnife;
import butterknife.Unbinder;

/**
 * 这只是一个分装的一个普通的Fragment抽象类,将一些通用的初始化进行包装
 * <p>
 */

public abstract class BaseCompatFragment extends Fragment {

    protected String TAG;
    protected Context mContext;
    protected Activity mActivity;
    private Unbinder binder;


    private View mRootView = null;

    @Override
    public void onAttach(Context context) {
        mActivity = (Activity) context;
        mContext = context;
        super.onAttach(mContext);
    }

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

        if(mRootView == null) {
            mRootView = inflater.inflate(getLayoutId(), container, false);
        }

        if(mRootView != null) {
            ViewGroup parent = (ViewGroup) mRootView.getParent();
            if(parent != null) {
                parent.removeView(mRootView);
            }
        }
        //注册组件间通信框架EventBus

        if(!EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().register(this);
        }

        return mRootView;
    }

    /******************懒加载*****************/

    protected boolean isViewInitiated;
    protected boolean isVisibleToUser;
    protected boolean isDataInitiated;
    protected boolean isFragmentHidden;
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        this.isVisibleToUser = isVisibleToUser;
        prepareFetchData();

    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        isFragmentHidden= hidden;

    }

    public boolean prepareFetchData() {
        return prepareFetchData(false);
    }

    public boolean prepareFetchData(boolean forceUpdate) {
        if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) {
            lazyLoad();
            isDataInitiated = true;
            return true;
        }
        return false;
    }

    /**
     * 加载要显示的数据,由子类去具体实现
     */
    protected abstract void lazyLoad();

    /*************************************/


    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        TAG = getClass().getSimpleName();
        //绑定butterKnife
        binder = ButterKnife.bind(this, view);
        getBundle(getArguments());
        isViewInitiated = true;
        prepareFetchData();
        initData();
        initUI(view, savedInstanceState);

        onLoadingView();

    }

    public void initData(){

    }

    public void openStartActivity(Class clazz) {
        Intent intent = new Intent(this.getActivity(), clazz);
        startActivity(intent);
    }

    /**
     * 首次加载布局
     */
    protected abstract void onLoadingView();

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if (binder != null)
            binder.unbind();
    }

    @Override
    public void onDetach() {
        super.onDetach();
    }

    @Override
    public void onDestroy() {
        if(EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().unregister(this);
        }
        super.onDestroy();
    }

    @LayoutRes
    public abstract int getLayoutId();

    /**
     * 得到Activity传进来的值
     */
    public void getBundle(Bundle bundle) {
    }

    /**
     * 初始化UI
     */
    public abstract void initUI(View view, @Nullable Bundle savedInstanceState);

}
package com.model.singlecode.base.mvp;


/**
 *
 * <p>
 * Mvp Fragment基类,同样是通过泛型将mvp框架引入到Fragment中来,实现Fragment与泛型mvp模块的绑定
 * <p>
 */

public abstract class BaseMVPCompatFragment<P extends BasePresenter, M extends IBaseModel> extends
        BaseCompatFragment implements IBaseView {
    public P mPresenter;
    public M mIMode;


    /**
     * 在监听器之前把数据准备好
     */
    public void initData() {
        super.initData();

        mPresenter = (P) initPresenter();
        if (mPresenter != null) {
            mIMode = (M) mPresenter.getModel();
            if (mIMode != null) {
                mPresenter.attachMV(mIMode, this);
            }
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mPresenter != null) {
            mPresenter.detachMV();
        }
    }


    @Override
    protected void onLoadingView() {
           //子类可以重写并在这里实现加载数据时的加载动画等操作
     }

}
package com.model.singlecode.mvpdemo;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.View;

import com.model.singlecode.base.mvp.BaseMVPCompatFragment;
import com.model.singlecode.base.mvp.BasePresenter;
import com.model.singlecode.basemvp.R;
import com.model.singlecode.mvpdemo.mvp.contract.MainContract;
import com.model.singlecode.mvpdemo.mvp.presenter.MainPresenter;

import java.util.List;

import androidx.annotation.Nullable;

/**
 * 创建时间:2019/5/20
 * 创建人:singleCode
 * 功能描述:基于继承mvp Fragment基类,mvp框架的使用实例
 **/
public class MainFragment extends BaseMVPCompatFragment<MainContract.MainPresenter,MainContract.MainModel>
implements MainContract.MainView {
    @Override
    protected void lazyLoad() {
        //可以再这里实现懒加载
        mPresenter.LoadData();
    }

    @Override
    public int getLayoutId() {
        return R.layout.fragment_main;
    }

    @Override
    public void initUI(View view, @Nullable Bundle savedInstanceState) {
        // 初始化UI

    }

    @NonNull
    @Override
    public BasePresenter initPresenter() {
        return new MainPresenter();
    }

    @Override
    public void onLoadData(List<String> list) {
        //获取到加载的数据结果
    }

    @Override
    protected void onLoadingView() {
        super.onLoadingView();
        //重写,并实现加载动画等
    }
}

上面就是整个基于mvp的应用框架,代码量很少,但是却能够帮我们实现所有Android应用界面和功能都基于MVP整体框架来进行开发。有需要源码的童鞋可以去我的github下载源码.

mvp模式,虽然让我们的代码结构更加清晰,解耦比MVC更彻底,但是mvp模式有个缺点就是,会给我的代码带来大量的接口,想象一下,如果我们这没有contract模块,每个界面就会对应出现至少三层接口,那会出现多少接口文件?在我们的解决方案就是增加contract模块进行接口管理。那么是否还有更牛的框架呢?答案是肯定的,他就是MVVM。

三、MVVM模式

MVVM可以算是MVP的升级版,其中的VM是ViewModel的缩写,ViewModel可以理解成是View的数据模型和Presenter的合体,ViewModel和View之间的交互通过Data Binding完成,而Data Binding可以实现双向的交互,这就使得视图和控制层之间的耦合程度进一步降低,关注点分离更为彻底,同时减轻了Activity的压力。

但是到目前为止,我所接触的项目以及和我一样在开发的朋友都很少接触到MVVM模式的使用。主要还是应用门槛比MVP要高一些,而且一般的中小型项目MVP模式就已经足够我们使用了。如果有兴趣的朋友可以去搜索并实战一些基于MVVM模式的框架。这里由于本人在这方面接触的知识水平有限,就不班门弄斧了!

发布了29 篇原创文章 · 获赞 3 · 访问量 907

猜你喜欢

转载自blog.csdn.net/LVEfrist/article/details/90342852