MVP案例解析

来源:互联网 发布:mac os 彻底删除软件 编辑:程序博客网 时间:2024/06/13 16:34

    • MVP的概念
    • MVP的作用
    • MVP的使用步骤
    • 检验MVP模式写的是否规范的办法
    • 案例解析

MVP的概念

MVP中,M层负责数据的读取和存储;V层负责视图逻辑的处理;P层负责业务逻辑的处理。同时,P层在处理业务逻辑时需要与V层和M层交互,所以会获取两层的引用实例,充当掌控者的角色。M层与V层彻底解耦的。

MVP是在MVC的基础上升级版,重在解耦,并不一定减少代码量。在大型项目中,引入MVP开发模式能充分体现出其强大功效。目前,一些敏捷开发的小型项目还是用MVC模式的较多。只是C层与V层的耦合度很容易变高,使得activity变得很臃肿。

MVP的作用

  1. 把Activity里的许多逻辑都抽离到View和Presenter的接口中去,并由具体的实现类来完成(用接口的形式因为有多种具体的实现),方便进行单元测试。

  2. Activity变得容易看懂,容易维护,以后要调整业务、删减功能也就变得简单许多。同时把业务逻辑抽到Presenter中去。

注意:由于activity/fragment是V层的实现类,视图逻辑可通过接口的形式在其中实现。

MVP的使用步骤

  1. 创建V层的接口,根据视图逻辑列出对应的接口。视图逻辑:即用户的操作中与视图相关的。如获取清除用户输入的数据,显示隐藏进度条/对话框,页面跳转等。

  2. 创建P层的接口,根据业务逻辑列出对应的接口。业务逻辑:即用户的操作中与视图无关的。如点击滑动事件的处理等。

  3. 在V层的实现类中(即activity/fragment)实例化Presenter的实现类。并在Presenter的实现类中获取V层和M层的引用,并调用V层实现类的中的视图逻辑方法。

检验MVP模式写的是否规范的办法

  1. Activity除了FindView、SetListener以及Init的代码。其他的就是对Presenter的调用,还有对View接口的实现。
  2. 试想着若界面改动或者业务逻辑改动,是否只影响到某一层逻辑的改动(逻辑复用操作产生的代码不是逻辑改动)。若M V P层都影响了,就说明没有解耦彻底。(注意:单个功能单个Presenter,若功能相近可单个Presenter内多个接口)
  3. P层的业务逻辑达到可以直接复用到其他V层而不需要该V层另做非视图逻辑外的处理(即逻辑复用)。
  4. V层不见业务逻辑处理。仅仅有视图逻辑处理。更不能有M层的逻辑处理。

注意:2中说明下,页面改动引起的业务逻辑改动在MVP模式中还不能达到仅在某一层中改动,MVVM是针对此种情况的改善版。

案例解析

案例情景:用户输入账户名和密码后,调用服务器登录接口,并显示返回数据。

最终效果图

从业务需求来看,罗列出各层的逻辑接口,并且各层统一由契约类约束,使用户对各层的功能一目了然,维护起来也方便。

契约类代码如下:

/** * Created by jinzifu on 2017/2/8. * 契约类来统一管理MVP模式中的Model View Presenter 的接口,使用户对各层的功能一目了然,维护起来也方便。 */public class LoginContract {    public interface View {        public void clear();        public String getUsername();        public String getPassword();        public void showProgress();        public void hideProgress();        public void error();        public void success();        public void showEmpty();        public void showInfo(String string);    }    public interface Presenter {        public void loginP();    }    public interface Model {        public void loginM(String username, String password, OnLoginListener onLoginListener);    }}

其中的clear接口我忘了用了[汗颜],可以看出各层都以接口的形式实现各层的逻辑。接口的形式便于扩展和单元测试,同一个接口可以有不同的实现类。

P层的实现类代码如下:

/** * Created by jinzifu on 2017/02/08 * 有时候为了实现一个复杂的业务,需要多个Presenter配合的。 * Presenter 获取的是V层的引用,因此是在UI线程中操作的。而M层的异步网络请求是在子线程的。 */public class LoginPresenterImpl extends BasePresenter<LoginContract.View> implements LoginContract.Presenter {    private LoginModelImpl hiModel;    private LoginContract.View view;    public LoginPresenterImpl(LoginContract.View view) {        hiModel = new LoginModelImpl();        this.view = view;    }    @Override    public void loginP() {        if (TextUtils.isEmpty(view.getPassword()) || TextUtils.isEmpty(view.getUsername())) {            view.showEmpty();            return;        }        view.showProgress();        hiModel.loginM(view.getUsername(), view.getPassword(), new OnLoginListener() {            @Override            public void loginSuccess(User user) {                view.success();                view.showInfo("账号:" + user.getUsername() + "\t\t密码:" + user.getPassword());            }            @Override            public void loginFailed() {                view.error();            }            @Override            public void finish() {                view.hideProgress();            }        });    }}

可以看到在其构造方法内获取的V层和M层的引用(不是把activity的view传过来的意思,因为activity做了V层的实现类,这里获取的是V层的引用实例)。

实现了P层接口的loginP方法,在这里处理业务逻辑。并通过V层的引用调用V层的接口实现方法。如 view.showEmpty()、view.showProgress()等。

我们看到了该类继承了BasePresenter这个类,下面看下源码:

/** * Created by jinzifu on 2017/2/9. * BasePresenter基类,在此引入弱引用,防止内存泄漏 */public class BasePresenter<T> {    protected Reference<T> viewRef;    public void attachView(T view) {        viewRef = new WeakReference<T>(view);    }    public void detachView() {        if (viewRef != null) {            viewRef.clear();            viewRef = null;        }    }}

可以看到在这个基类中,通过attachView将V层的引用由强引用转化为弱引用(强引用宁可报错也不会被垃圾回收器回收,弱引用可在内存不足时被回收),对内存泄漏做了规避。detachView方法是对该引用置空处理。

这里采用泛型是因为作为基类,用于适配不同类型的Presenter。

下面看看V层的实现类即activity的源码:

public class LoginActivity extends BaseMvpActivity<LoginContract.View, LoginPresenterImpl> implements LoginContract.View {    @BindView(R.id.username)    EditText username;    @BindView(R.id.password)    EditText password;    @BindView(R.id.login)    Button login;    @BindView(R.id.show_info)    TextView showInfo;    private LoginPresenterImpl hiPresenter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_mvp);        ButterKnife.bind(this);    }    @Override    protected LoginPresenterImpl createPresenter() {        hiPresenter = new LoginPresenterImpl(this);        return hiPresenter;    }    @OnClick(R.id.login)    public void onClick() {        hiPresenter.loginP();    }    @Override    public void clear() {        username.setText("");        password.setText("");    }    @Override    public String getUsername() {        return username.getText().toString();    }    @Override    public String getPassword() {        return password.getText().toString();    }    @Override    public void showProgress() {        Toast.makeText(this, "加载中...", Toast.LENGTH_SHORT).show();    }    @Override    public void hideProgress() {        Toast.makeText(this, "加载完毕...", Toast.LENGTH_SHORT).show();    }    @Override    public void error() {        Toast.makeText(this, "请求失败...", Toast.LENGTH_SHORT).show();    }    @Override    public void success() {        Toast.makeText(this, "请求成功...", Toast.LENGTH_SHORT).show();    }    @Override    public void showEmpty() {        Toast.makeText(this, "账号和密码不能为空", Toast.LENGTH_SHORT).show();    }    @Override    public void showInfo(String string) {        showInfo.setText(string);    }}

V层做的仅仅是和视图逻辑有关的接口实现方法以及Presenter的初始化。避免了逻辑冗余。用户的操作中设计到业务逻辑的可以通过调用Presenter的接口方法实现。

通过createPresenter方法获取Presenter实例。父类BaseMvpActivity< V,T>在该类中实现V层由强引用变成弱引用。并在V生命周期结束时引用置空。
父类源码如下:

/** * Created by jinzifu on 2017/2/9. */public abstract class BaseMvpActivity<V, T extends BasePresenter<V>> extends Activity {    protected T presenter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        presenter = createPresenter();        presenter.attachView((V) this);    }    @Override    protected void onDestroy() {        super.onDestroy();        presenter.detachView();    }    protected abstract T createPresenter();}

项目中后续的需求变更,可以通过P层的接口变更或者多个接口组合实现业务逻辑的变动。也可以仅仅改变其实现类的业务逻辑。

思考:MVP是为了降低耦合而产生的,代码量及类文件也许会增多。不过对于大型项目后续的维护很有帮助。业务分离出V层,各个业务通过接口实现,接口又利于拓展和多个具体实现。便于同一套业务逻辑独立抽出来用在不同的V层。

如何利于新手上手,并且在满足降耦的基础上减少代码量,是接下来我们要做的事情。敬等后续更新。

0 0