初识MVP模式

记得自己刚开始的时候学习MVP模式的时候,在网上扒拉了各种各种的文章,下载各种高start的Demo,不管三七二十对葫芦画瓢一步步搭建自己的项目。开始时其实也不是特别懂,每次按照模式化的步骤写V层、M层、P层,一步步写自己项目(其实内心觉得前期的准备工作好繁琐)。可能是写的熟练了,遇到了一些问题,慢慢的也一些想法,后来就想设计一个自己使用框架。
这个系列的文章也是把自己最近学习内容的一个汇总,以此自勉,继续前行。
那下面就开始正题了,我相信大家也看过不少关于MVP模式的文章,其中关于各个层的关系及其作用内容的讲解,并且列了一大堆图解说明,这些我们直接绕过去,直接上代码我想比什么都更直接更直观:

首先定义M层:

public interface MvpModel {
}

然后是V层:

public interface MvpView {
}

在定义一个P层对View的绑定和解绑,内存上的优化:

public interface MvpPresenter<V extends MvpView> {

void attachView(V view);//View的绑定

void detachView();//View的解绑

}

接下来我要实现一个具体的P层绑定:

 public class MvpBasePresenter<V extends MvpView> implements MvpPresenter<V> {

      private V view;

      public V getView() {
        return view;
      }

      @Override public void attachView(V view) {
        this.view = view;
      }

      @Override public void detachView() {
        this.view = null;
      }
    }

MvpBasePresenter中我们先这样简单的处理后面再优化这块代码,比如view使用弱引用、动态代理对view的判空处理等。
刚刚写的这一段代码,其实就是一个中介者模式,P层就是个抽象的中介者对象,V和M就是抽象同事对象,根据具体的业务实现的M和V就是具体的同事对象,用P层隔离M和V彼此不知道对方,以达到解耦的作用。M—V—P各自的角色作用以及之间的关系就很明朗了。

我们通过一件登陆的案例来实现刚刚写的代码:

V层->抽象同事B

public interface LoginView extends MvpView {

   void onLoginResult(String result);

}

M层->具体同事A

public class LoginModel implements MvpModel {

    public String login(String username, String password) {
        if (username.equals("wds") && password.equals("123456")) {
             return "登陆成功";
        } else {
             return "登陆失败";
        }

    }
 }

P->具体的中介

public class LoginPresenter extends MvpBasePresenter<LoginView> {

    // 持有同事引用
    // 两个同事:M层、V层
    private LoginModel model;

    public LoginPresenter() {
        this.model = new LoginModel();
    }

    public void login(String username, String password) {
        String loginState = this.model.login(username, password);
        this.getView().onLoginResult(loginState);

    }

}

在Activity中使用

public class MainActivity extends AppCompatActivity implements LoginView {

    private LoginPresenter  loginPresenter;

    private EditText        et_name;

    private EditText        et_password;

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et_name = findViewById(R.id.et_name);
        et_password = findViewById(R.id.et_password);
        findViewById(R.id.login).setOnClickListener(new View.OnClickListener() {

            @Override public void onClick(View v) {
                loginPresenter.login(et_name.getText().toString(), et_password.getText().toString());
            }
        });

        loginPresenter = new LoginPresenter();//创建P成
        loginPresenter.attachView(this);//绑定
    }

    @Override public void onLoginResult(String result) {
        Log.e("TAG", "result :" + result);
    }

    @Override protected void onDestroy() {
        super.onDestroy();
        loginPresenter.detachView();//解绑
    }
}

这样就完成了一个简单的MVP模式,在M层处理数据如http请求、数据库查询,在P层管理M和V处理业务逻辑,其实有时候会省略M层,直接在P层处理数据,但我个人还是习惯写M层。写到这里其实已经把MVP最重要的内容(,M和V各自处理自己的内容,通过P层关联M和V,以达到解耦作用),但如果我们在实际的开发中这样写代码,估计要吐血,步骤很繁琐,重复操作很多,这就不是一个合格的程序猿该做的事。

接下就对刚刚写的内容做一个简单的封装和优化,抽取一个抽象的MvpActivity,把一些共同的操作都放在这里统一处理。

在使用P层的时候,有两个重要的步骤,一个绑定(loginPresenter.attachView(this))和解绑(loginPresenter.detachView()),需要在每个使用P的Activity都要进行的操作。
如下面这代码:

public abstract class MvpActivity<V extends MvpView, P extends MvpPresenter<V>> extends AppCompatActivity implements MvpView {

    private P presenter;

    public P getPresenter() {
        return presenter;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (presenter == null){
            this.presenter = createPresenter();
        }
        if (presenter != null){
            this.presenter.attachView((V)this);
        }
    }

    public abstract P createPresenter();

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

代码很简单,定义一个抽象方法createPresenter,通过向下的泛型设计,返回值为继承了MvpPresenter的Presenter,在onCreate中创建P,并做了attachView绑定,在onDestroy执行detachView解绑。

在MvpBasePresenter的attachView方法中,通过动态代理对view判空进行统一的处理,这样就不用在每次使用view前进行不等于空的处理,代码如下:

public class MvpBasePresenter<V extends MvpView> implements MvpPresenter<V> {

    //弱引用
    private V viewProxy;
    private V view;

    public V getView() {
        return viewProxy;
    }

    @Override
    public void attachView(V view) {
        this.view = view;
        //目标接口->实际上是MvpView
        Class<?>[] interfaces = view.getClass().getInterfaces();
        try {
            viewProxy = (V)Proxy.newProxyInstance(view.getClass().getClassLoader(), interfaces, new ProxyInvocationHandler<V>(view));
        } catch (Exception e){
            e.printStackTrace();
        }

    }

    @Override
    public void detachView() {
        this.view = null;
    }

    @Override
    public void destory() {

    }

    private class ProxyInvocationHandler<V extends MvpView> implements InvocationHandler {

        private V view;
        public ProxyInvocationHandler(V view){
            this.view = view;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (view != null){
                //执行
                return method.invoke(view, args);
            }
            //不存在->报错了
            throw new NullPointerException("空异常");
        }
    }

}

最后在A具体ctivity的实现由原来的代码就变成如下:

public class MainActivity extends MvpActivity<LoginView,LoginPresenter> implements LoginView {


    private EditText        et_name;

    private EditText        et_password;

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et_name = findViewById(R.id.et_name);
        et_password = findViewById(R.id.et_password);
        findViewById(R.id.login).setOnClickListener(new View.OnClickListener() {

            @Override public void onClick(View v) {
                getPresenter().login(et_name.getText().toString(), et_password.getText().toString());
            }
        });

    }

    @Override public LoginPresenter createPresenter() {
        return new LoginPresenter();
    }

    @Override public void onLoginResult(String result) {
        Log.e("TAG", "result :" + result);
    }

}

今天的内容就到这了😁😁


风后面是风,天空上面是天空,而你的生活可以与众不同

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容

  • 一、什么是MVP 在介绍MVP之前,大家一定会想到MVC,我们首先简单介绍一下MVC,因为掌握了MVC对我们学习M...
    怡红快绿阅读 361评论 0 0
  • 前言 谈起MVC,MVP和MVVM这三个最耳熟能详的Android框架,相信大家对它们都不陌生,但在实际的情况下,...
    ghroost阅读 3,877评论 0 40
  • 原文 前言 本文为回答一位朋友关于MVC/MVP/MVVM架构方面的疑问所写, 旨在介绍iOS下MVC/MVP/M...
    无沣阅读 710评论 1 2
  • DBinding权威使用指南 标签(空格分隔): dbing 使用方式 layout: Activity: 一、设...
    天之界线2010阅读 1,377评论 0 4
  • MarkDown语法入门 一、标题 标题是文章中常用的格式,用1到6个#号来标志1到6号的标题,想要把一段文字定义...
    xxxQinli阅读 255评论 0 1