前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MVP模式的经典封装

MVP模式的经典封装

作者头像
蜻蜓队长
发布2018-08-03 11:29:13
8910
发布2018-08-03 11:29:13
举报
文章被收录于专栏:Android机动车Android机动车

人之所以能,是相信能。

说到MVP,大家应该都不陌生了,由于其高度解等等优点,越来越多的项目使用这个设计模式。然而,优点虽在,缺点也不少,其中一个就是类多了很多,而且V与P直接要项目通信,那么P就得持有V得实例,但如果活动挂掉了,如果没有对V进行释放,还有导致内存溢出得问题,而且,那么多的接口函数,看得到人眼花缭乱,也使得很多人在使用这个模式的时候望而尚步。

回归正题,最近在进行代码重构,决定采用MVP模式进行开发。如果我们不进行封装,单纯地简单使用MVP来开发,这要就会出现如上的问题,接口和类多而且重复。和别人协同开发也存在问题。那么对MVP模式进行封装就显得很重要了。当然,一千个人中有一千个哈姆雷特,这里提供一下我的思路,供大家参考。

什么是MVP模式

MVP模式相当于在MVC模式中又加了一个Presenter用于处理模型和逻辑,将View和Model完全独立开,在android开发中的体现就是activity仅用于显示界面和交互,activity不参与模型结构和逻辑。

使用MVP模式会使得代码多出一些接口但是使得代码逻辑更加清晰,尤其是在处理复杂界面和逻辑时,我们可以对同一个activity将每一个业务都抽离成一个Presenter,这样代码既清晰逻辑明确又方便我们扩展。当然如果我们的业务逻辑本身就比较简单的话使用MVP模式就显得,没那么必要。所以我们不需要为了用它而用它,具体的还是要要业务需要。

其实,简而言之:view就是UI,model就是数据处理,而persenter则是他们的纽带。

使用MVP的结构
再对比下MVC

MVP模式还是存在一些不足之处的,最大的不足就是类的快速增多,但相对于MVC的臃肿、MVP的高度解耦来说,类的增多可能就洒洒水啦~

封装思路

上图介绍:

Contract:契约类,一个功能模块中View接口、Model接口和请求数据回调统一在对应模块的Contract中定义,便于管理。

ViewInterface: view层接口,定义了view中的UI操作

ModelInterface: model层接口,定义了model负责的数据操作方法,如请求接口,操作数据库等

CallbackInterface: model层操作数据完成后的回调

BasePersenter: Persenter父类,主要是对相关view的获取,销毁等操作

View: view层实现类,主要就是Activity或Fragment,负责UI展示和事件响应

Model: model层实现类,就是依据业务,请求对应接口或数据库,并将结果返给回调CallBack

Persenter: persenter层类,负责业务逻辑处理,view将响应传给persenter,persenter负责调用model,并将结果返回给view供其展示

框架封装

1、Presenter的封装

代码语言:javascript
复制
/**
 * Description: Presenter的根父类
 * Created by jia on 2016/10/27.
 * 人之所以能,是相信能
 */
public abstract class BasePresenter<T> {
    //View接口类型的软引用
    protected Reference<T> mViewRef;
    public void attachView(T view) {
        //建立关系
        mViewRef = new SoftReference<T>(view);
    }
    protected T getView() {
        return mViewRef.get();
    }
    public boolean isViewAttached() {
        return mViewRef != null && mViewRef.get() != null;
    }
    public void detachView() {
        if (mViewRef != null) {
            mViewRef.clear();
        }
    }
}

先来观察下这个base类:

先设置一泛型T,T为与presenter相关的view。BasePresenter中持有一个view的软引用。

在关联方法中将view对象传入,并存入软引用中,创建获取、取消关联和判断方法。

至于使用软引用,是为了防止所持的view都销毁了,但presenter一直持有,导致内存泄漏。

2、view的封装

view的封装,主要是BaseActivity和BaseFragment的封装。

2.1、BaseActivity

代码语言:javascript
复制
public abstract class BaseActivity<V, T extends BasePresenter<V>> extends FragmentActivity {
    public String TAG = getClass().getSimpleName() + "";
    protected T mPresenter;
    public Context mContext;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ... 
        initActivityView(savedInstanceState);
        mContext = BaseActivity.this;
        //创建presenter
        mPresenter = createPresenter();
        findViewById();
        getData();
    }
    @Override
    protected void onResume() {
        super.onResume();
        if (null != mPresenter) {
            mPresenter.attachView((V) this);
        } 
    }
    /**
     * 关于Activity的界面填充的抽象方法,需要子类必须实现
     */
    protected abstract void initActivityView(Bundle savedInstanceState);
    /**
     * 加载页面元素
     */
    protected abstract void findViewById();
    /**
     * 创建Presenter 对象
     *
     * @return
     */
    protected abstract T createPresenter();
    protected abstract void getData();
    @Override
    protected void onDestroy() {
        super.onDestroy();
        ...
        if (null != mPresenter) {
            mPresenter.detachView();
        }
    }
}

BaseActivity设置两个泛型——V和P,明显地,分别代表对应的View和Presenter。

其持有一个BasePresenter,在onCreated方法中,使用createPresenter方法返回对应的BasePresenter的子类,我们就可以使用了。

这里注意一下view和presenter的处理:在onCreated中创建Presenter对象,但其内部的view软引用还是空;在onResume中关联view,此时presenter已经持有view的软引用;当然,还需要在onDestroy中取消关联。

至于其他的封装就不再介绍了,相信大家肯定还有更优的封装方法。

2.2 BaseFragment

代码语言:javascript
复制
public abstract class BaseFragment<V, T extends BasePresenter<V>> extends Fragment {
    public String TAG = getClass().getSimpleName() + "";
    private static final String STATE_SAVE_IS_HIDDEN = "STATE_SAVE_IS_HIDDEN";
    protected T mPresenter;
    //定义一个View用来保存Fragment创建的时候使用打气筒工具进行的布局获取对象的存储
    protected View view;
    /**
     * 当Fragment进行创建的时候执行的方法
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter = createPresenter();//创建presenter
    }
    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putBoolean(STATE_SAVE_IS_HIDDEN, isHidden());
    }
    /**
     * 这个方法是关于Fragment完成创建的过程中,进行界面填充的方法,该方法返回的是一个view对象
     * 在这个对象中封装的就是Fragment对应的布局
     */
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        view = initFragmentView(inflater);
        return view;
    }
    /**
     * 这个方法当onCreateView方法中的view创建完成之后,执行
     * 在inflate完成view的创建之后,可以将对应view中的各个控件进行查找findViewById
     */
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        initFragmentChildView(view);
    }
    /**
     * 这个方法是在Fragment完成创建操作之后,进行数据填充操作的时候执行的方法
     */
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initFragmentData(savedInstanceState);
    }
    /**
     * 完成打气筒操作
     */
    protected abstract View initFragmentView(LayoutInflater inflater);
    /**
     * 进行findViewById的操作
     *
     * @param view 打气筒生成的View对象
     */
    protected abstract void initFragmentChildView(View view);
    /**
     * 网络数据填充的操作
     *
     * @param savedInstanceState
     */
    protected abstract void initFragmentData(Bundle savedInstanceState);
    /**
     * 创建Presenter对象
     */
    protected abstract T createPresenter();
    @Override
    public void onResume() {
        super.onResume();
        if (null != mPresenter) {
            mPresenter.attachView((V) this);
        }
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        if (null != mPresenter) {
            mPresenter.detachView();
        }
    }
}

BaseFragment与BaseA类似就不再累赘。

3、Contract契约类

契约,顾名思义,规范定义,定义功能和模板。

在契约类中定义View的接口,Model的接口。因为Model将数据返给Presenter是使用回调方式,所以还需要再契约类中定义对应的回调。

具体看示例吧。

实战

这里我们以登录功能模块为例:

1、契约类

代码语言:javascript
复制
/**
 * Description:
 * Created by jia on 2017/12/20.
 * 人之所以能,是相信能
 */
public class LoginContract {
    public interface LoginView{
        void onCheckFormatSuccess();
        void onCheckFormatFail(String info);
        void onLoginSuccess(Login login);
        void onLoginFail(String errorInfo);
    }
    public interface LoginModel{
        void login(String name,String password,LoginCallBack callBack);
    }
    public interface LoginCallBack{
        void onSuccess(Login login);
        void onFail(String errorInfo);
    }
}

这里定义了登录页面的view接口、model接口和对应的回调。

在view中,只定义与UI展示的相关方法,如检查账号密码格式成功(失败)、登录成功(失败)等。

model负责数据请求,所以在接口中只定义了登录的方法。

回调定义了登录成功还是失败的方法。

2、Model实现类

代码语言:javascript
复制
/**
 * Description: 登录 Model实现类
 * Created by jia on 2017/12/20.
 * 人之所以能,是相信能
 */
public class LoginModelImpl implements LoginContract.LoginModel {
    /**
     * 登录方法
     * @param name
     * @param password
     * @param callBack
     */
    @Override
    public void login(String name, String password, final LoginContract.LoginCallBack callBack) {
        LoginNetUtils.getInstance().login(name, password, new BaseSubscriber<Login>() {
            @Override
            public void onSuccess(Login login) {
                callBack.onSuccess(login);
            }
            @Override
            public void onFail(String info) {
                callBack.onFail(info);
            }
        });
    }
}

创建Model实现类,重写其登录方法既可,将登录接口交给回调。

3、Presenter

代码语言:javascript
复制
/**
 * Description: 登录主持类
 * Created by jia on 2017/12/20.
 * 人之所以能,是相信能
 */
public class LoginPresenter extends BasePresenter<LoginContract.LoginView> {
    private LoginModelImpl model;
    public LoginPresenter() {
        model = new LoginModelImpl();
    }
    /**
     * 检查格式
     *
     * @param name
     * @param password
     */
    public void checkFormat(String name, String password) {
        if (TextUtils.isEmpty(name)) {
            getView().onCheckFormatFail("请输入用户名");
        } else if (TextUtils.isEmpty(password)) {
            getView().onCheckFormatFail("请输入密码");
        } else if (password.length() < 6 || password.length() > 18) {
            getView().onCheckFormatFail("密码格式不正确");
        } else {
            getView().onCheckFormatSuccess();
            login(name, password);
        }
    }
    /**
     * 登录
     *
     * @param name
     * @param password
     */
    public void login(String name, String password) {
        model.login(name, password, new LoginContract.LoginCallBack() {
            @Override
            public void onSuccess(Login login) {
                getView().onLoginSuccess(login);
            }
            @Override
            public void onFail(String errorInfo) {
                getView().onLoginFail(errorInfo);
            }
        });
    }
}

LoginPresenter集成BasePresenter,传入LoginView最为泛型T。

内部持有Model实现类对象。

创建两个方法,一个是检查格式,一个是登录。两个方法就是业务的处理。

如登录方法,登录返回后,在回调中得到数据,也可以再进行一些逻辑判断,将结果交给view的对应的方法。

注意这里使用getView()方法就可以,因为在父类中getView方法直接返回的对应的view实例。

4、View

代码语言:javascript
复制
/**
 * 登录界面
 */
public class LoginActivity extends BaseActivity<LoginContract.LoginView, LoginPresenter>
        implements LoginContract.LoginView, View.OnClickListener {
    ...
    @Override
    protected void initActivityView(Bundle savedInstanceState) {
        setContentView(R.layout.activity_login);
    }
    @Override
    protected void findViewById() {
        ...
    }
    @Override
    protected LoginPresenter createPresenter() {
        return new LoginPresenter();
    }
    @Override
    protected void getData() {
    }
    @Override
    public void onCheckFormatSuccess() {
        loading.show();
    }
    @Override
    public void onCheckFormatFail(String info) {
        RxToast.error(mContext, info).show();
    }
    @Override
    public void onLoginSuccess(Login login) {
        ...
    }
    @Override
    public void onLoginFail(String errorInfo) {
       ...
    }
    @Override
    public void onClick(View view) {
        ...
    }
    ...
}

这里的LoginActivity就是登录功能模块的view,其集成BaseActivity,传入view和presenter泛型。

实现LoginView接口,重写接口定义好的UI方法。

在createPresenter方法中创建LoginPresenter对象并返回。这样,就可以使用mPresenter直接操作逻辑了。

再看下整个功能模块的事件流和数据流

大致就是这样了,有不足的地方大家多提意见。^_^

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-01-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android机动车 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是MVP模式
    • 使用MVP的结构
      • 再对比下MVC
      • 封装思路
      • 框架封装
        • 1、Presenter的封装
          • 2、view的封装
            • 2.1、BaseActivity
              • 2.2 BaseFragment
                • 3、Contract契约类
                • 实战
                  • 1、契约类
                    • 2、Model实现类
                      • 3、Presenter
                        • 4、View
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档