设计模式:从mvc到mvp

来源:互联网 发布:全球高清网络电视直播 编辑:程序博客网 时间:2024/06/16 03:27

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的理解。

0 0
原创粉丝点击