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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AspNetForums 的汉化版和改写版,主要改写在于: 1、修改了邮件发送程序,设计了可发送需要身份验证的邮件发送组件。 2、设计了一个统一用户群组件(OneUsers.dll),只要你使用 OneUsers 的用户群(数据库可以使用我们的也可以使用你们自己的,我们提供 OneUsers 的数据库的 SQL 脚本),用户只要在加入本站用户群的任何一个论坛注册一次,就可以在其他论坛以同样的帐号登陆。 比如,你在 www.chinamvp.org 注册一次,那么你也可以用此帐号在 www.onebbs.net 登陆,而无需再次注册。 安装文档 1、必须安装MS SQL SERVER 2、必须安装.Net Framework + IIS 1、安装 MS SQL SERVER 论坛数据库文件 首先在 查询分析器里执行 aspnetforums_oneuser1.0.sql脚本安装论坛数据库 然后是 CreateDefaultData1.0.sql 安装默认数据 oneuser1.0.sql 是我们统一使用的用户群库,你可以不安装,而是使用我们的默认数据库,这个默认数据库在我们的服务器上。 如果你是英文系统 请执行 aspnetforums_oneuser1.0forEnSqlServer.sql 和 oneuser1.0forEnSqlServer.sql 但是不能保证论坛 能够正常执行 2、设置 AspNetForums 里的web.config <add key="connectionString" value="server=localhost; User ID=sa; Password=;database=aspnetforums" /> aspnetforums数据库 用户名和密码 <add key="connectionString" value="server=localhost; User ID=sa;database=OneUser" /> oneuser数据库 用户名和密码,你可以删除他或者注释掉,那么就是默认使用我们服务器上的统一用户库 3、设置邮件服务器 <add key="smtpServer" value="yourhost.com" /> <!-- Can specify SMTP Server to use to send out emails. Use "default" to use the default Windows 2000 SMTP Server --> <add key="ChinaASPXMailer" value="True" /> <!-- 如果你使用我们定制的邮件发送组件,请设为 True --> <add key="UserName" value="[email protected]" /> <!--ChinaASPXMailer s UserName ,For Auth mail server --> <add key="Password" value="test" /> <!--ChinaASPXMailer s Password ,For Auth mail server --> <add key="Port" value="25" /> <!-- Port for ChinaASPXMailer s prot --> <add key="FromName" value="ChinaMVP.COM" /> <!-- FromName for ChinaASPXMailer s FromName --> <!--add key="Charset" value="GB2312" /--> <!-- FromName for ChinaASPXMailer s FromName ,Default value is gb2312 --> 把以上的 yourhost.com 改成你的邮件服务器 如果需要验证,请设置 UserName,否则 删除或者注释掉 4 、设置IIS,在你的web站点里设置一个虚拟目录 其物理目录指向 AspNetForums 目录。 5、解决方案 EngineAspNetForums.sln 6、管理员帐号: user:admin pwd:admin

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值