设计模式:从mvc到mvp

mvc与mvp概述

MVP是Model(数据) View(界面) Presenter(表现层)的缩写,它是MVC架构的变种,强调Model和View的最大化解耦和单一职责原则
Model:负责数据的来源和封装,比如网络请求类,数据库操作类以及java bean,如果有必要则提供接口暴露自己处理数据的状态和进度。
View:负责UI相关,如布局UI的初始化,各种listener的设置。在Android中,我们通常写的Activity和Fragment就是属于View层;在web开发中,html则是View层。
Controller:业务逻辑控制器,主要负责当获取到数据后对数据进行逻辑处理,然后将数据绑定到View上;比如:请求一个url,从网络获取到数据,进行解析javabean,然后各种set数据。对于控制器的概念大家很好理解,因为我们每天都在这样做,在Activity中请求数据然后更新UI。但是结合View的概念来看,很显然Activity和Fragment不但承担了View的任务,还负责完成的Controller的功能,随着业务功能的增多,Activity的代码越来越难以阅读和维护,这就是在Android中使用MVC的弊端,为了解决MVC模式下View层的臃肿,MVP模式应运而生。
Presenter:专门从C独立出来的业务逻辑层,主要负责处理原先View层的业务逻辑,解决了Activity的臃肿问题,让Activity只负责处理UI,职责更加明确;并且将View层的业务逻辑抽取到P层之后,View层与Model层也实现了解耦;便于后期代码的扩展和维护,并且业务逻辑层独立后代码还得到很大的重用性。
下面我用具体的代码的实例来分析,我以项目中的登录模块作为例子:
这里我啰嗦俩句吧:初学者可能会错误的认为MVP模式就是把数据层的代码写在modle包下面,界面写在view,业务逻辑写在presenter包中,MVP架构也就仅此而已,在此我需要特别强调一点的就是:
即使你进行的 分包,但是你的类之间互相持有对方的引用(就是在类内部互相new),互相依赖,那么分不分包是没有丝毫意义的,
就跟java语言中的封装一样,如果你写了个phone类,却又担负起照相、看电影等责任,那么谈抽象,谈封装就没什么意义。或者,写了一个照相的方法,还要把修照片的等多个功能也加在这个方法里,那么这个方法也就仅仅这当前用而已。
对于一个程序员来说,最重要的不是看代码逻辑,而要把更多的注意力放在代码优化,很多程序员不知道自己努力的方向吗,而说起来很简单,就是下面几句话:
如何让自己的代码 低藕合,高复用,易测试,好维护

mvc回顾

好了,继续MVP模式的讲解,为了更好的理解MVP,我先通过具体代码回顾下mvc,然后对比mvp,先用一个mvc设计模式写一个登录逻辑代码。

public class LoginActivity extends AppCompatActivity {

    @InjectView(R.id.username)
    EditText etUsername;
    @InjectView(R.id.pwd)
    EditText etPwd;
    @InjectView(R.id.activity_login)
    LinearLayout activityLogin;

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

    // 登录按钮的点击事件
    @OnClick(R.id.activity_login)
    public void onClick() {
        // 1、获取输入框的数据
        String username = etUsername.getText().toString().trim();
        String pwd = etPwd.getText().toString().trim();

        // 2、 检查用户输入的合法性
        if(checkInput(username, pwd)){
            // 3、 执行登录操作
            String url = "www.baidu.com";
            HttpUtil.getInstance().post(url, new   HttpUtil.HttpUtilCallBack() {
                @Override
                public void onSuccess(String response) {
                    // 4、登录成功,保存用户数据
                    saveUserInfo();
                    // 5、跳转界面
                    jumpActivity();
                }

                @Override
                public void onFail(String errorInfo) {
                    // 登录失败
                }
            });
        }

    }

    private boolean checkInput(String username, String pwd) {
        if(TextUtils.isEmpty(username) && TextUtils.isEmpty(pwd)){
            showEmptyInfo();
            return false;
        }
        if(username.length() != 11){
            showLenthErrorInfo();
            return false;
        }
        return true;
    }

    private void showEmptyInfo() {
        Toast.makeText(this, "用户名或密码不能为空", Toast.LENGTH_SHORT).show();
    }


    private void showLenthErrorInfo() {
        Toast.makeText(this, "用户名长度不正确", Toast.LENGTH_SHORT).show();
    }

    private void saveUserInfo() {
    }

    private void jumpActivity() {
    }

}

下面是我虚拟的一个网络请求,就是我们Model层的代码

public class HttpUtil {
    private HttpUtil() {}
    private static HttpUtil httpUtil = new HttpUtil();
    public static HttpUtil getInstance(){
        return httpUtil;
    }

    public void post(String url, HttpUtilCallBack callBack){
       ......
    }

    public interface HttpUtilCallBack{
        void onSuccess(String response);
        void onFail(String errorInfo);
    }
}

从上面的代码中可以看到,MVC模式下弊端,很显然Activity不但承担了View的任务,还负责完成的Controller的功能,随着业务功能的增多,Activity的代码越来越难以阅读和维护,这就是在Android中使用MVC的弊端。

从mvc中抽取出p层

下面从上面的Activity中抽取出一个P 层,继续来看代码,我们不去动原来的Mode层。 很简单的抽取出一个p(LoginPresenter)层,把业务逻辑放到p层中,原来的LoginActivity仅仅作为一个View
下面是抽取后的LoginActivity的代码:

public class LoginActivity extends AppCompatActivity{

    @butterknife.InjectView(R.id.username)
    EditText etUsername;
    @butterknife.InjectView(R.id.pwd)
    EditText etPwd;
    @butterknife.InjectView(R.id.activity_login)
    LinearLayout activityLogin;

    private LoginPresenterImpl mLoginPresenterImpl;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        butterknife.ButterKnife.inject(this);

        mLoginPresenterImpl = new LoginPresenterImpl(this);
    }

    @butterknife.OnClick(R.id.activity_login)
    public void onClick() {
        String username = etUsername.getText().toString().trim();
        String pwd = etPwd.getText().toString().trim();

        mLoginPresenterImpl.login(username, pwd);
    }

    public void showEmptyInfo() {
        Toast.makeText(this, "用户名或密码不能为空", Toast.LENGTH_SHORT).show();
    }


    public void showLenthErrorInfo() {
        Toast.makeText(this, "用户名长度不正确", Toast.LENGTH_SHORT).show();
    }
}

先不看LoginPresneter的代码,也先不去看MVC中LoginActivity中的代码,就看MVP中的Activity,仅仅作为了一个View界面的展示,明白了这一点,可以去对比MVC中的LoginActivity了,看看那些地方改动。对比之后,下面贴出抽取出的P层代码:

public class LoginPresenterImpl  {
    private LoginActivity mLoginActivity;

    public LoginPresenterImpl(LoginActivity loginActivity) {
        mLoginActivity = loginActivity;
    }

    public void login(String username, String pwd) {
        // 2、 检查用户输入的合法性
        if(checkInput(username, pwd)){
            // 3、 执行登录操作
            String url = "www.baidu.com";
            HttpUtil.getInstance().post(url, new HttpUtil.HttpUtilCallBack() {

                @Override
                public void onSuccess(String response){
                    // 保存用户信息
                    saveUserInfo();
                    // 界面跳转
                    jumpActivity();
                }

                @Override
                public void onFail(String errorInfo) {
                }
            });
        }
    }

    private boolean checkInput(String username, String pwd) {
        if(TextUtils.isEmpty(username) && TextUtils.isEmpty(pwd)){
            mLoginActivity.showEmptyInfo();
            return false;
        }
        if(username.length() != 11){
            mLoginActivity.showLenthErrorInfo();
            return false;
        }
        return true;
    }

    private void saveUserInfo() {
    }

    private void jumpActivity() {
    }
}

看以看到业务逻辑的代码在LoginPresneter中。
但是,还是有很大的不足,耦合性太强,我们仔细分析代码就可以看到,在LoginActivity中有创建了一个LoginPresenter实例,而在LoginPresenter中创建了一个LoginActivity实例对象,V层和P之间互相依赖,谈何 低藕合,高复用,易测试的模块化设计。

p层和v层实现解耦操作

下面我们对P层和V层进行一个解耦操作,首先,创建一个LoginActivity的接口类,这个类里面,主要是一些P层业务逻辑代码执行结果的回调。
这里写图片描述
我们看到在P层和V层明显多了一个借口,
先来看看我们的LoginPresenter接口里面的代码

public interface LoginPresenter {
    void login(String username, String pwd);
}

没错,就是把原来的Presnter类里面的业务方法进行了统一的规制声明
而LoginView借口里面则是业务逻辑执行结果的一些回调。

public interface LoginView {
    void onLoginState(boolean success, String msg);
}

下面是新的LoginPresenterimpl的代码

public class LoginPresenterImpl  implements LoginPresenter{
    private LoginView mLoginView;

    public LoginPresenterImpl(LoginView loginView) {
        mLoginView = loginView;
    }

    public void login(String username, String pwd) {
        // 2、 检查用户输入的合法性
        if(checkInput(username, pwd)){
            // 3、 执行登录操作
            String url = "www.baidu.com";
            HttpUtil.getInstance().post(url, new HttpUtil.HttpUtilCallBack() {

                @Override
                public void onSuccess(String response){
                    mLoginView.onLoginState(true, response); // 业务逻辑执行成功的回调
                    // 保存用户信息
                    saveUserInfo();
                    // 界面跳转
                    jumpActivity();
                }

                @Override
                public void onFail(String errorInfo) {
                    mLoginView.onLoginState(false, errorInfo); // 业务逻辑执行失败的回调
                }
            });
        }
    }

    private boolean checkInput(String username, String pwd) {
        if(TextUtils.isEmpty(username) && TextUtils.isEmpty(pwd)){
            return false;
        }
        if(username.length() != 11){
            return false;
        }
        return true;
    }

    private void saveUserInfo() {
    }

    private void jumpActivity() {
    }
}

可以很清楚的看到,现在LoginPresenterImpl类里面不再有LoginActivity的引用,而仅仅是一个LoginView的接口,LoginPresenterImpl就不仅仅属于LoginActivity,而属于所有实现了LoginView接口的类。实现了LoginPresenter的解耦,下面再看LoginActivity里面的代码呢

public class LoginActivity extends AppCompatActivity implements LoginView{

    @butterknife.InjectView(R.id.username)
    EditText etUsername;
    @butterknife.InjectView(R.id.pwd)
    EditText etPwd;
    @butterknife.InjectView(R.id.activity_login)
    LinearLayout activityLogin;

    private LoginPresenter mLoginPresenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        butterknife.ButterKnife.inject(this);

        mLoginPresenter = new LoginPresenterImpl(this);
    }

    @butterknife.OnClick(R.id.activity_login)
    public void onClick() {
        String username = etUsername.getText().toString().trim();
        String pwd = etPwd.getText().toString().trim();

        mLoginPresenter.login(username, pwd);
    }

    public void showEmptyInfo() {
        Toast.makeText(this, "用户名或密码不能为空", Toast.LENGTH_SHORT).show();
    }


    public void showLenthErrorInfo() {
        Toast.makeText(this, "用户名长度不正确", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onLoginState(boolean success, String msg) {
        if(success){
            // 登录成功
        } else {
            // 登录失败
        }
    }
}

对,大家看到了,在LoginActivity还是创建了Loginpresneterimpl的实例对象,还是没有实现彻底的解耦。而我要说的mvp模式,到这里也基本结束,这个具体解释看完下面的总结,大家应该能理解。

总结

对MVP模式,每个人都有自己的理解,谷歌官方也给出了MVP代码,谷歌对MVP的理解把Acitivity看成一个数据、界面。控制器之外的另一个控制器,仅仅控制自己生命周期,仅此而已,而界面View展示则交给fragment去展示。但是没有太多人去跟随谷歌,
大多数人还是把Activity看成是一个View,包括我也一样。mvp有优点,也有缺点,优缺点在我上面代码从mvc到mvp一个过程也一目了然,一路下来,功能还是那个功能,当时类却暴增,仅仅一个登陆模块,就需要这么类,对于一些中小型项目来说,使用mvp未免让项目更加复杂了,要是再去为继续解耦,就得继续往上抽取,在上层进行一个对象的实例化,像谷歌那样,还要为每一个Activity创建一个Fragment,那创建的类还是要更多,所以,上面解耦操作到此为适中。
当然,以上只是个人对mvp的理解。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值