设计模式——Android 常用设计模式之MVP详解及项目实战

引言

Android经过这几年的不断发展壮大,APP的功能越来越强大,UI也越来越复杂,对于Android开发者来说UI层在程序开发过程中担任了越来越多的职责。通常一个APP是由多种数据模型(Model)和多种视图(View)组成,如果我们直接使用Model-View设计模型,那这将使得我们的程序代码变得复杂、耦合度高、不利于单元测试和代码重构。

一、MVP概述

这里写图片描述
MVP的全称为Model-View-Presenter,MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。作为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的人机交互都发生在Presenter内部,简而言之,View和Model层不直接交互,所有交互都是同P层来实现,在MVP模式下的Presenter要拥有“绝对权力”。如果没有它,
Model与View就是两个孤岛,尽管各有各的地盘(完全解耦),但不能发挥基本的作用。通过MVP中的View就真的代码精简了蛮多,View只要从相应的IView接口下实现相应的属性和一些简单方法就完事了,而最终调用IView接口下的那个视图实例则完全交给了Presenter。引用微软官方的一张图直观体会下MVP
这里写图片描述

二、MVP中四种重要角色及联系

这里写图片描述
虽然MVP的全称为Model-View-Presenter,看起来只有三种角色ModePresenterView,实质上是四种角色——ViewView interfaceModelPresenter

1、View层

负责绘制UI元素,直接承担与用户进行交互的职能,在Android中通常体现为Activity、Fragment、View、Dialog等,在实际开发中也可以根据整个项目的业务对于所有View共性的可以抽象到一个基类。简单理解View 层只承担负责调用Presenter层职责

2、View Interface层

实质上是对View层的逻辑抽象,是需要View必须要实现的接口View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试,在实际开发中也可以根据具体业务进行抽象到一个基类,一般具体的View interface 最好只对应一个View ,虽然理论上是可以对应多个View的,建议最好不要,于是比例应该是View:View Interface=1:1

3、Presenter层

实质上是作为View(实际上是View Interface)的逻辑代理实现(View层的具体逻辑实现应该放在Presenter层),自然是应该与View 一一对应,虽然我们从结构上能让一个Presenter为多个View提供逻辑支持。但是这样会导致Presenter里面的逻辑对到底实现哪个View产生一定的混乱,为了简单明了(个人观点)除了需要与View 交互,同时还需要去与Model交互,简而言之,Presenter层充当的的连接Model与View的桥梁作用,那么在代码上自然需要持有View Interface层和Model的引用,还是个人建议,为了简捷明了比例应该是Presenter:View Interface=1:1。

4、Model层

负责实现具体业务功能,Model层里才是功能的本质,其他三种角色最终通信的目的就是调用Model层的,主要负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合)等。这里值得再注明的是M不直接和Activity等直接关联,也不一定和V层、Presenter 层一一对应,一种比较好的习惯是按照功能模块来封装Model层,通常Model层除了真正的功能实现之外,还可以通过一些回调来反馈结果。结合以上的理解,完整的MVP模式比例应该是V:P:M=1:1:n(个人建议,仅供参考)总而言之,MVP最根本的目的就是使M和V层解耦,同时降低Activity、Fragment等View层代码的臃肿程度。

三、MVP模式的优点和不足

1、MVP模式的优点

  • 有效降低了耦合度,实现了Model和View真正的完全分离

  • 各功能模块职责划分明显,层次清晰

  • 项目结构更加灵活,增强了代码可复用性

  • 隐藏数据

  • 降低耦合度,,可以修改View而不影响Modle

  • Model可以被复用

  • 降低了单元测试的成本

  • View可以进行组件化。在MVP当中,View不直接依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务完全无知。它要做的只是调用P。这样就可以做到高度可复用的View组件。

  • 把业务逻辑抽到Presenter中去,避免后台线程引用着Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM

2、MVP模式的不足

  • Presenter中除了应用逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较臃肿,维护起来会比较麻烦。

  • 额外的代码复杂度及学习成本

四、Android MVP实现的基本形式

这里写图片描述

1、定义View Interface 层

通常个人建议建议,可以把所有需要与P、V交互的操作抽象到View Interface 层。

ILoginView.java

package com.crazymo.mvpdemo.view.activity;

/**
 * Auther: Crazy.Mo
 * DateTime: 2017/8/30 17:06
 * Summary:
 */
public interface ILoginView {
    void showLoading();
    void hideLoading();
    void toMainActivity();
    void showUsernameErro();//仅仅在逻辑上去判断
    void showPwdErro();
    void onUsernameErro();//Model回调
    void onLoginowPwdErro();
}

2、实现View 层

通常View 层除了需要完成绘制界面的任务之外,还必须要承担另外两份职责——实现View Interface 层接口持有对应的Presenter引用并初始化以及在不使用的时候释放引用,因为MVP模式与传统的M-V模式不同,V不直接和M交互,而是V通过P间接完成与M的交互,所以要想通过调用P就必须得持有对应的引用。

LoginActivity.java

package com.crazymo.mvpdemo.view.activity;

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

import com.crazymo.mvpdemo.R;
import com.crazymo.mvpdemo.presenter.LoginPresenterImpl;

import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class LoginActivity extends AppCompatActivity implements ILoginView {

    @Bind(R.id.edt_user)
    EditText edtUser;
    @Bind(R.id.edt_pwd)
    EditText edtPwd;
    @Bind(R.id.progress_login)
    ProgressBar progressLogin;
    @Bind(R.id.btn_login)
    Button btnLogin;
    private LoginPresenterImpl presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        presenter=new LoginPresenterImpl(this);
        ButterKnife.bind(this);
    }

    @Override
    public void showLoading() {
        progressLogin.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideLoading() {
        progressLogin.setVisibility(View.VISIBLE);
    }

    @Override
    public void toMainActivity() {
        MainActivity.showMainActivity(this);
        finish();
    }

    @Override
    public void showUsernameErro() {
        edtUser.setError("Username can't be empty or Username is too short");
    }

    @Override
    public void showPwdErro() {
        edtPwd.setError("Password can't be empty !");
    }

    @Override
    public void onUsernameErro() {
        edtUser.setError("Username is not exits!");
    }

    @Override
    public void onLoginowPwdErro() {
        edtPwd.setError("Username or password is erro!");
    }

    @OnClick({R.id.btn_login})
    void onClick(View view){
        presenter.login(edtUser.getText().toString(),edtPwd.getText().toString());
    }

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

3、实现Presenter层

通常Presenter层作为M层和V层的桥梁,P层自然发挥承上启下的作用,所以P层的基本实现一般都会是持有对应的M并且初始化M,然后在V中调用P的时候实现在P内部通过M的引用调用M,这可以认为是启下的作用,而通常V层都需要根据M层的反馈结果做出不同的响应,所以还需要持有V的引用以及给M使用的回调。

ILoginPresenter.java

package com.crazymo.mvpdemo.presenter;

/**
 * Auther: Crazy.Mo
 * DateTime: 2017/8/30 17:06
 * Summary:
 */
public interface ILoginPresenter {
    void login(String user, String pwd);//此例是以登录为例,而通常登录这个操作需要与M交互,因此可以把这个操作抽象到P层

    void detachView();//这个是释放对应的view ,可以封装到一个基类中
}

IOnLoginFinishListener.java

package com.crazymo.mvpdemo.presenter;

/**
 * Auther: Crazy.Mo
 * DateTime: 2017/8/30 17:18
 * Summary:
 */
public interface IOnLoginFinishListener {
    void onLoginSuccess();
    void onLoginFailed(String type);
}

LoginPresenterImpl.java

package com.crazymo.mvpdemo.presenter;

import com.crazymo.mvpdemo.model.ILoginModel;
import com.crazymo.mvpdemo.model.LoginModelImpl;
import com.crazymo.mvpdemo.view.activity.ILoginView;

/**
 * Auther: Crazy.Mo
 * DateTime: 2017/8/30 18:03
 * Summary:
 */
public class LoginPresenterImpl implements ILoginPresenter ,IOnLoginFinishListener{
    public static final String USER_NOT_EXITS = "user_not_exits";
    public static final String USER_PWD_ERRO = "user_pwd_erro";
    private ILoginView loginView;//P需要与V 交互,所以需要持有V的引用
    private ILoginModel loginModel;
    public LoginPresenterImpl(ILoginView view) {
        this.loginView = view;
        this.loginModel = new LoginModelImpl(this);
    }
    @Override
    public void login(String user, String pwd) {

        if(loginView!=null) {
            loginView.showLoading();
            if ("".equals(user)) {
                loginView.showUsernameErro();
                loginView.hideLoading();
                return;
            }
            if ("".equals(pwd)){
                loginView.showPwdErro();
                loginView.hideLoading();
                return;
            }
            loginModel.login(user,pwd);
        }
    }

    @Override
    public void detachView() {
        //为了避免内存泄漏,在不用的时候及时释放所持有的View 引用
        loginView=null;
    }

    @Override
    public void onLoginSuccess() {
        loginView.toMainActivity();
    }

    @Override
    public void onLoginFailed(String type) {
        loginView.hideLoading();
        if(USER_NOT_EXITS.equals(type)){
            loginView.onUsernameErro();
        }else if(USER_PWD_ERRO.equals(type)){
            loginView.onLoginowPwdErro();
        }
    }
}

4、实现Model 层

Model 层作为真正的业务功能实现层,由于需要把结果反馈到V层,所以需要通过P层的回调来实现。

ILoginModel.java

package com.crazymo.mvpdemo.model;

/**
 * Auther: Crazy.Mo
 * DateTime: 2017/8/30 16:56
 * Summary:
 */
public interface ILoginModel {
    void login(String user,String name);
}

LoginModelImpl.java

package com.crazymo.mvpdemo.model;

import android.util.Log;

import com.crazymo.mvpdemo.presenter.IOnLoginFinishListener;
import com.crazymo.mvpdemo.presenter.LoginPresenterImpl;

/**
 * Auther: Crazy.Mo
 * DateTime: 2017/8/31 9:20
 * Summary:
 */
public class LoginModelImpl implements ILoginModel {
    public static final String DEFAULT_USER = "crazymo_";
    public static final String DEFAULT_PWD = "cmo";
    private IOnLoginFinishListener finishListener;
    public LoginModelImpl(IOnLoginFinishListener listener){
        this.finishListener=listener;
    }

    @Override
    public void login(String user,String pwd) {
        int k=0;
        for(int i=0;i<100000;i++){
            k++;
        }
        Log.e("CrazyMo_",Thread.currentThread().getName().toString());
        if(DEFAULT_USER.equals(user)&& DEFAULT_PWD.equals(pwd)){
            finishListener.onLoginSuccess();
        }else if(!(DEFAULT_USER.equals(user))){
            finishListener.onLoginFailed(LoginPresenterImpl.USER_NOT_EXITS);
        }else if(DEFAULT_USER.equals(user)&&!(DEFAULT_PWD.equals(pwd))){
            finishListener.onLoginFailed(LoginPresenterImpl.USER_PWD_ERRO);
        }
    }
}

这里写图片描述

五、Android MVP实现的结合泛型和弱引用的形式

1、View层(View Interface层代码不变参见源码)

package com.crazyview.mvppro.activity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;

import com.crazyview.mvppro.R;
import com.crazyview.mvppro.presenter.LoginPresenterImpl;

import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class LoginActivity extends BaseActivity<LoginActivity,LoginPresenterImpl> implements ILoginView {

    @Bind(R.id.edt_user)
    EditText edtUser;
    @Bind(R.id.edt_pwd)
    EditText edtPwd;
    @Bind(R.id.progress_login)
    ProgressBar progressLogin;
    @Bind(R.id.btn_login)
    Button btnLogin;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        ButterKnife.bind(this);
    }

    @Override
    public void showLoading() {
        progressLogin.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideLoading() {
        progressLogin.setVisibility(View.VISIBLE);
    }

    @Override
    public void toMainActivity() {
        MainActivity.showMainActivity(this);
        finish();
    }

    @Override
    public void showUsernameErro() {
        edtUser.setError("Username can't be empty or Username is too short");
    }

    @Override
    public void showPwdErro() {
        edtPwd.setError("Password can't be empty !");
    }

    @Override
    public void onUsernameErro() {
        edtUser.setError("Username is not exits!");
    }

    @Override
    public void onLoginowPwdErro() {
        edtPwd.setError("Username or password is erro!");
    }

    @OnClick({R.id.btn_login})
    void onClick(View view){
        presenter.login(edtUser.getText().toString(),edtPwd.getText().toString());
    }

    @Override
    protected void onResume() {
        super.onResume();
        presenter.attachView(this);//在Activity里的生命周期方法中完成Presenter和V的绑定并初始化
    }

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

    @Override
    public LoginPresenterImpl initPresenter() {
        return new LoginPresenterImpl();
    }
}

2、Presenter 层

封装了一个BasePresenter,同时把View的引用改为弱引用,在View 层的生命周期方法中完成Presenter的管理。

package com.crazyview.mvppro.presenter;

import java.lang.ref.WeakReference;

/**
 * Auther: Crazy.Mo
 * DateTime: 2017/9/7 17:10
 * Summary:
 */
public abstract class BasePresenter<T> {
    protected WeakReference<T> modelView;

    public void attachView(T view) {
        this.modelView = new WeakReference<T>(view);
    }

    public void dettachView() {
        this.modelView.clear();
    }

    protected T getView() {
        return modelView.get();
    }
}

继承BasePresenter实现具体的业务Presenter

package com.crazyview.mvppro.presenter;

import com.crazyview.mvppro.activity.LoginActivity;
import com.crazyview.mvppro.model.ILoginModel;
import com.crazyview.mvppro.model.LoginModelImpl;

/**
 * Auther: Crazy.Mo
 * DateTime: 2017/8/30 18:03
 * Summary:
 */
public class LoginPresenterImpl extends BasePresenter<LoginActivity> implements ILoginPresenter ,IOnLoginFinishListener{
    public static final String USER_NOT_EXITS = "user_not_exits";
    public static final String USER_PWD_ERRO = "user_pwd_erro";
   //P需要与M 交互,所以需要持有M的引用
    private ILoginModel loginModel;
    public LoginPresenterImpl() {
        this.loginModel = new LoginModelImpl(this);
    }
    @Override
    public void login(String user, String pwd) {

        if(getView()!=null) {
            getView().showLoading();
            if ("".equals(user)) {
                getView().showUsernameErro();
                getView().hideLoading();
                return;
            }
            if ("".equals(pwd)){
                getView().showPwdErro();
                getView().hideLoading();
                return;
            }
            loginModel.login(user,pwd);
        }
    }

    @Override
    public void onLoginSuccess() {
        getView().toMainActivity();
    }

    @Override
    public void onLoginFailed(String type) {
        getView().hideLoading();
        if(USER_NOT_EXITS.equals(type)){
            getView().onUsernameErro();
        }else if(USER_PWD_ERRO.equals(type)){
            getView().onLoginowPwdErro();
        }
    }
}

3、Model层

package com.crazyview.mvppro.model;

import android.util.Log;

import com.crazyview.mvppro.presenter.IOnLoginFinishListener;
import com.crazyview.mvppro.presenter.LoginPresenterImpl;

/**
 * Auther: Crazy.Mo
 * DateTime: 2017/8/31 9:20
 * Summary:
 */
public class LoginModelImpl implements ILoginModel {
    public static final String DEFAULT_USER = "crazymo_";
    public static final String DEFAULT_PWD = "cmo";
    private IOnLoginFinishListener finishListener;
    public LoginModelImpl(IOnLoginFinishListener listener){
        this.finishListener=listener;
    }

    @Override
    public void login(String user,String pwd) {
        int k=0;
        for(int i=0;i<100000;i++){
            k++;
        }
        Log.e("CrazyMo_",Thread.currentThread().getName().toString());
        if(DEFAULT_USER.equals(user)&& DEFAULT_PWD.equals(pwd)){
            finishListener.onLoginSuccess();
        }else if(!(DEFAULT_USER.equals(user))){
            finishListener.onLoginFailed(LoginPresenterImpl.USER_NOT_EXITS);
        }else if(DEFAULT_USER.equals(user)&&!(DEFAULT_PWD.equals(pwd))){
            finishListener.onLoginFailed(LoginPresenterImpl.USER_PWD_ERRO);
        }
    }
}

完整源码传送门

小结

MVP当然还有其他的实现形式,为了适配更多类型的View还可以考虑泛型等,如果你真正的理解面向接口编程,灵活使用好抽象和接口,你也可以改造出更高级的MVP模式,比如说Google 官方正在维护的Android 架构等其他第三方的项目。以上只是个人对于MVP使用的一些经验总结和感悟。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CrazyMo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值