Android框架MVC、MVP和MVVM汇总探究

本文深入探讨了MVC、MVP和MVVM三种软件架构模式,通过实例对比了它们在Android开发中的应用及优缺点。MVC通过Controller控制Model层数据更新并在View层展示;MVP通过Presenter解耦View与Model,增强测试性;MVVM采用数据双向绑定,简化View与Model交互。
摘要由CSDN通过智能技术生成

稍作修改。

1、介绍

MVC、MVP、MVVM这些模式是为了解决开发过程中的实际问题而提出来的,目前作为主流的几种架构模式而被广泛使用。本文代码

2、了解并区分MVC,MVP,MVVM

2.1 MVC

MVC,(Model View Controller),是软件架构中最常见的一种框架,简单来说就是通过controller的控制去操作model层的数据,并且返回给view层展示,具体见下图

在这里插入图片描述


当用户发触事件的时候,view层会发送指令到controller层,接着controller去通知model层更新数据,model层更新完数据以后直接显示在view层上,这就是MVC的工作原理。

Android中的MVC:

对于原生的Android项目来说,layout.xml里面的xml文件就对应于MVC的view层,里面都是一些view的布局代码,而各种java bean,还有一些类似repository类就对应于model层,至于controller层嘛,当然就是各种activity咯。比如你的界面有一个按钮,按下这个按钮去网络上下载一个文件,这个按钮是view层的,是使用xml来写的,而那些和网络连接相关的代码写在其他类里,比如你可以写一个专门的networkHelper类,这个就是model层,那怎么连接这两层呢?是通过 button.setOnClickListener()这个函数,这个函数就写在了activity中,对应于controller层。是不是很清晰。

存在的问题:

  • 问题就在于xml作为view层,控制能力实在太弱了,你想去动态的改变一个页面的背景,或者动态的隐藏/显示一个按钮,这些都没办法在xml中做,只能把代码写在activity中,造成了activity既是controller层,又是view层的这样一个窘境。大家回想一下自己写的代码,如果是一个逻辑很复杂的页面,activity或者fragment是不是动辄上千行呢?这样不仅写起来麻烦,维护起来更是噩梦。
  • view层和model层是相互可知的,这意味着两层之间存在耦合,耦合对于一个大型程序来说是非常致命的,因为这表示开发,测试,维护都需要花大量的精力。

正因为MVC存在上述的问题,所以引入后面要介绍的两个框架-MVP和MVVM。

2.2 MVP

MVP,(Model View Presenter)作为MVC的演化,解决了MVC不少的缺点,对于Android来说,MVP的model层相对于MVC是一样的,而activity和fragment不再是controller层,而是纯粹的view层,所有关于用户事件的转发全部交由presenter层处理。下面还是让我们看图

在这里插入图片描述

从图中就可以看出,最明显的差别就是view层和model层不再相互可知,完全的解耦,取而代之的presenter层充当了桥梁的作用,用于操作view层发出的事件传递到presenter层中,presenter层去操作model层,并且将数据返回给view层,整个过程中view层和model层完全没有联系。看到这里大家可能会问,虽然view层和model层解耦了,但是view层和presenter层不是耦合在一起了吗?其实不是的,对于view层和presenter层的通信,我们是可以通过接口实现的;具体的意思就是说我们的activity,fragment可以去实现实现定义好的接口,而在对应的presenter中通过接口调用方法。不仅如此,我们还可以编写测试用的View,模拟用户的各种操作,从而实现对Presenter的测试。这就解决了MVC模式中测试,维护难的问题。

注意:其实最好的方式是使用fragment作为view层,而activity则是用于创建view层(fragment)和presenter层(presenter)的一个控制器。

2.3 MVVM

MVVM,(Model View ViewModel)如果说MVP是对MVC的进一步改进,那么MVVM则是思想的完全变革。它是将“数据模型数据双向绑定”的思想作为核心,因此在View和Model之间没有联系,通过ViewModel进行交互,而且Model和ViewModel之间的交互是双向的,因此视图的数据的变化会同时修改数据源,而数据源数据的变化也会立即反应到View上。结构如下图所示:

在这里插入图片描述

3 案例

案列背景介绍:

用户点击一个按钮A,获取github上对应公司对应仓库中贡献排行第一的任的名字,然后我们还会有一个按钮B,
用户点击按钮B,界面上排行第一的那个人的名字就会换成自己的。效果如图所示:

3.1 MVC-案列

首先看对应view层的xml文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/container"
    android:orientation="vertical"
    tools:context=".MainActivity"
    android:fitsSystemWindows="true">
    <Button
        android:text="get"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="get"/>
    <Button
        android:text="change"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="change"/>
    <TextView
        android:id="@+id/top_contributor"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textSize="30sp"/>
</LinearLayout>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

接着看对应controller层的activity:

public class MainActivity extends AppCompatActivity {

    private ProcessDialog dialog;
    private Contributor contributor = new Contributor();

    private TextView topContributor;

    private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {

        @Override
        public void onStart() {
            showProgress();
        }

        @Override
        public void onCompleted() {

        }

        @Override
        public void onError(Throwable e) {

        }

        @Override
        public void onNext(Contributor contributor) {
            MainActivity.this.contributor = contributor;
            topContributor.setText(contributor.login);
            dismissProgress();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        topContributor = (TextView)findViewById(R.id.top_contributor);
    }

    public void get(View view){
        getTopContributor("square", "retrofit");//进行网络请求
    }

    public void change(View view){
        contributor.login = "GITHUB";
        topContributor.setText(contributor.login);
    }

    public void getTopContributor(String owner,String repo){
        GitHubApi.getContributors(owner, repo)
                .take(1)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.newThread())
                .map(new Func1<List<Contributor>, Contributor>() {
                    @Override
                    public Contributor call(List<Contributor> contributors) {
                        return contributors.get(0);
                    }
                })
                .subscribe(contributorSub);
    }

    public void showProgress(){
        if(dialog == null){
            dialog = new ProcessDialog(this);
        }
        dialog.showMessage("正在加载...");
    }

    public void dismissProgress(){
        if(dialog == null){
            dialog = new ProcessDialog(this);
        }
        dialog.dismiss();
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

我们看一下get()方法中调用的getTopContributor方法

    public void getTopContributor(String owner,String repo){  
        GitHubApi.getContributors(owner, repo)  
                .take(1)  
                .observeOn(AndroidSchedulers.mainThread())  
                .subscribeOn(Schedulers.newThread())  
                .map(new Func1<List<Contributor>, Contributor>() {  

                    @Override  
                    public Contributor call(List<Contributor> contributors) {  
                        return contributors.get(0);  
                    }  
                })  
                .subscribe(contributorSub);  
    }  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

熟悉rxjava和retrofit的同学应该都明白这是啥意思,如果对这两个开源库不熟悉也没事,可以参考rxjavaretrofit 对于这里大家只要知道这段代码的意思就是去获取github上owner公司中的repo仓库里贡献排名第一的那个人。贡献者是通过Contributor这个java bean存储的。

  public class Contributor {  

        public String login;  
        public int contributions;  

        @Override  
        public String toString() {  
            return login + ", " + contributions;  
        }  
    }  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

很简单,login表示贡献者的名字,contributor表示贡献的次数。

然后通过rxjava的subscriber中的onNext()函数得到这个数据。

  private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {  

        @Override  
        public void onStart() {  
            showProgress();  
        }  

        @Override  
        public void onCompleted() {  

        }  

        @Override  
        public void onError(Throwable e) {  

        }  

        @Override  
        public void onNext(Contributor contributor) {  
            MainActivity.this.contributor = contributor;  
            topContributor.setText(contributor.login);  
            dismissProgress();  
        }  
    };  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

对于另外一个change按钮的功能相信大家都能看懂了。

流程小结:

1、在xml中写好布局代码;
2、activity作为一个controller,里面的逻辑是监听用户点击按钮并作出相应的操作。比如针对get按钮,做的工作就是调用GithubApi的方法去获取数据。
3、GithubApi,Contributor等类则表示MVC中的model层,里面是数据和一些具体的逻辑操作。

3.2 MVP-案列

通过具体的代码大家知道了MVC在Android上是如何工作的,也知道了它的缺点,那MVP是如何修正的呢?这里先向大家推荐github上的一个第三方库,通过这个库大家可以很轻松的实现MVP。好了,还是看代码吧。

首先还是xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/container"
    android:orientation="vertical"
    tools:context=".ui.view.MainActivity"
    android:fitsSystemWindows="true">
    <Button
        android:text="get"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="get"/>
    <Button
        android:text="change"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="change"/>
    <TextView
        android:id="@+id/top_contributor"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textSize="30sp"/>
</LinearLayout>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

这个和MVC是一样的,毕竟界面的形式是一样的嘛。

接下去,我们看一个接口

public interface ContributorView extends MvpView {
    void onLoadContributorStart();
    void onLoadContributorComplete(Contributor topContributor);
    void onChangeContributorName(String name);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

这个接口起什么作用呢?还记得我之前说的吗?MVP模式中,view层和presenter层靠的就是接口进行连接,而具体的就是上面的这个了,里面定义的三个方法,

  • 第一个是开始获取数据;
  • 第二个是获取数据成功;
  • 第三个是改名。

我们的view层(activity)只要实现这个接口就可以了。

下面看activity的代码

public class MainActivity extends MvpActivity<ContributorView,ContributorPresenter> implements ContributorView {

    private ProcessDialog dialog;
    private TextView topContributor;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        topContributor = (TextView)findViewById(R.id.top_contributor);
    }

    @NonNull
    @Override
    public ContributorPresenter createPresenter() {
        return new ContributorPresenter();
    }

    public void get(View view){
        getPresenter().get("square", "retrofit");
    }

    public void change(View view){
        getPresenter().change();
    }

    @Override
    public void onLoadContributorStart() {
        showProgress();
    }

    @Override
    public void onLoadContributorComplete(Contributor contributor) {
        topContributor.setText(contributor.toString());
        dismissProgress();
    }

    @Override
    public void onChangeContributorName(String name) {
        topContributor.setText(name);
    }

    public void showProgress(){
        if(dialog == null){
            dialog = new ProcessDialog(this);
        }
        dialog.showMessage("正在加载...");
    }

    public void dismissProgress(){
        if(dialog == null){
            dialog = new ProcessDialog(this);
        }
        dialog.dismiss();
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

它继承自MvpActivity,实现了刚才的ContributorView接口。继承的那个MvpActivity大家这里不用太关心主要是做了一些初始化和生命周期的封装。我们只要关心这个activity作为view层,到底是怎么工作的。

    public void get(View view){  
        getPresenter().get("square", "retrofit");  
    }  

    public void change(View view){  
        getPresenter().change();  
    }  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

get()和change()这两个方法是我们点击按钮以后执行的,可以看到,里面完完全全没有任何和model层逻辑相关的东西,只是简单的委托给了presenter,那我们再看看presenter层做了什么

public class ContributorPresenter extends MvpBasePresenter<ContributorView> {

    private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {

        @Override
        public void onStart() {
            ContributorView view = getView();
            if(view != null){
                view.onLoadContributorStart();
            }
        }

        @Override
        public void onCompleted() {
        }

        @Override
        public void onError(Throwable e) {
        }

        @Override
        public void onNext(Contributor topContributor) {
            ContributorView view = getView();
            if(view != null){
                view.onLoadContributorComplete(topContributor);
            }
        }
    };

    public void get(String owner,String repo){
        GitHubApi.getContributors(owner, repo)
                .take(1)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.newThread())
                .map(new Func1<List<Contributor>, Contributor>() {

                    @Override
                    public Contributor call(List<Contributor> contributors) {
                        return contributors.get(0);
                    }
                })
                .subscribe(contributorSub);
    }

    public void change(){
        ContributorView view = getView();
        if(view != null){
            view.onChangeContributorName("GITHUB");
        }
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

其实就是把刚才MVC中activity的那部分和model层相关的逻辑抽取了出来,并且在相应的时机调用ContributorView接口对应的方法,而我们的activity是实现了这个接口的,自然会走到对应的方法中。(其实质就是利用接口回调)

流程小结:

1、在xml中写好布局代码,将activity作为了view层,通过代码也可以看到,整个activity没有任何和model层相关的逻辑代码,
2、presenter获取了model层的数据之后,通过接口的形式将view层需要的数据返回给View层了。
3、GithubApi,Contributor等类则表示MVP中的model层,里面是数据和一些具体的逻辑操作。

好处:

首先,activity的代码逻辑减少了,其次,view层和model层完全解耦,具体来说,如果你需要测试一个http请求是否顺利,你不需要写一个activity,只需要写一个java类,实现对应的接口,presenter获取了数据自然会调用相应的方法,相应的,你也可以自己在presenter中mock数据,分发给view层,用来测试布局是否正确。

3.3 MVVM-案列

首先在看这段内容之前,你需要保证你对data binding框架有基础的了解。不了解的同学可以去看下这篇文章。在接下去让我们开始探索MVVM,MVVM最近在Android上可谓十分之火,最主要的原因就是谷歌推出了data binding这个框架,可以轻松的实现MVVM。

直接上代码:

首先看对应view层的xml文件

    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="contributor" type="zjutkz.com.mvvm.viewmodel.Contributor"/>
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/container"
        android:orientation="vertical"
        android:fitsSystemWindows="true">
        <Button
            android:id="@+id/get"
            android:text="get"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="get"/>
        <Button
            android:id="@+id/change"
            android:text="change"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="change"/>
        <TextView
            android:id="@+id/top_contributor"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:textSize="30sp"
            android:text="@{contributor.login}"/>
    </LinearLayout>
</layout>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

通过上面的代码可以看出,有些地方和MVC、MVP不同:新增 data标签,TextView 数据由变量引入。

MainActivity代码:

public class MainActivity extends AppCompatActivity {

    private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {

        @Override
        public void onStart() {
            showProgress();
        }

        @Override
        public void onCompleted() {

        }

        @Override
        public void onError(Throwable e) {

        }

        @Override
        public void onNext(Contributor contributor) {
            binding.setContributor(contributor);

            dismissProgress();
        }
    };

    private ProcessDialog dialog;

    private MvvmActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.mvvm_activity_main);
    }

    public void get(View view){
        getContributors("square", "retrofit");
    }

    public void change(View view){
        if(binding.getContributor() != null){
            binding.getContributor().setLogin("zjutkz");
        }
    }

    public void showProgress(){
        if(dialog == null){
            dialog = new ProcessDialog(this);
        }

        dialog.showMessage("正在加载...");
    }

    public void dismissProgress(){
        if(dialog == null){
            dialog = new ProcessDialog(this);
        }

        dialog.dismiss();
    }

    public void getContributors(String owner,String repo){
        GitHubApi.getContributors(owner, repo)
                .take(1)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.newThread())
                .map(new Func1<List<Contributor>, Contributor>() {

                    @Override
                    public Contributor call(List<Contributor> contributors) {
                        return contributors.get(0);
                    }
                })
                .subscribe(contributorSub);
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

如果你对data binding框架是有了解的,上面的代码你能轻松的看懂。 那model层又是什么呢?当然就是那些和数据相关的类,GithubApi等等。
重点来了,viewmodel层呢?好吧,viewmodel层就是是Contributor类!大家不要惊讶,我慢慢的来说。

public class Contributor extends BaseObservable{

    private String login;
    private int contributions;

    @Bindable
    public String getLogin(){
        return login;
    }

    @Bindable
    public int getContributions(){
        return contributions;
    }

    public void setLogin(String login){
        this.login = login;
        notifyPropertyChanged(BR.login);
    }

    public void setContributions(int contributions){
        this.contributions = contributions;
        notifyPropertyChanged(BR.contributions);
    }

    @Override
    public String toString() {
        return login + ", " + contributions;
    }


}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

我们可以看到,Contributor和MVP相比,继承自了BaseObservable,有基础的同学都知道这是为了当Contributor内部的variable改变的时候ui可以同步的作出响应。

我为什么说Contributor是一个viewmodel呢。大家还记得viewmodel的概念吗?view和viewmodel相互绑定在一起,viewmodel的改变会同步到view层,从而view层作出响应。这不就是Contributor和xml中那些组件元素的关系吗?所以,大家不要被binding类迷惑了,data binding框架中的viewmodel是自己定义的那些看似是model类的东西!比如这里的Contributor!

话说到这里,那binding类又是什么呢?其实具体对应到之前MVVM的那张图就很好理解了,我们想一下,binding类的工作是什么?

    binding = DataBindingUtil.setContentView(this, R.layout.mvvm_activity_main);  
    binding.setContributor(contributor);  
 
 
  • 1
  • 2

首先,binding要通过DataBindingUtil.setContentView()方法将xml,也就是view层设定。接着,通过setXXX()方法将viewmodel层注入进去。
由于这两个工作,view层(xml的各个组件)和viewmodel层(contributor)绑定在了一起。好了,大家知道了吗,binding类,其实就是上图中view和viewmodel中间的那根线啊!!

4 拓展

前面讨论了MVC,MVP和MVVM具体的实现方案,大家肯定都了解了它们三者的关系和使用方式。但是,这里我想说,不要把一个框架看作万能的,其实MVP和MVVM都是有自己的缺陷的!

MVP:
MVP的问题在于,由于我们使用了接口的方式去连接view层和presenter层,这样就导致了一个问题,如果你有一个逻辑很复杂的页面,你的接口会有很多,十几二十个都不足为奇。想象一个app中有很多个这样复杂的页面,维护接口的成本就会非常的大。
这个问题的解决方案就是你得根据自己的业务逻辑去斟酌着写接口。你可以定义一些基类接口,把一些公共的逻辑,比如网络请求成功失败,toast等等放在里面,之后你再定义新的接口的时候可以继承自那些基类,这样会好不少。

MVVM:
MVVM的问题其实和MVC有一点像。data binding框架解决了数据绑定的问题,但是view层还是会过重,大家可以看我上面那个MVVM模式下的activity:

public class MainActivity extends AppCompatActivity {  

    private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {  

        @Override  
        public void onStart() {  
            showProgress();  
        }  

        @Override  
        public void onCompleted() {  

        }  

        @Override  
        public void onError(Throwable e) {  

        }  

        @Override  
        public void onNext(Contributor contributor) {  
            binding.setContributor(contributor);  

            dismissProgress();  
        }  
    };  

    private ProcessDialog dialog;  

    private MvvmActivityMainBinding binding;  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        binding = DataBindingUtil.setContentView(this, R.layout.mvvm_activity_main);  
    }  

    public void get(View view){  
        getContributors("square", "retrofit");  
    }  

    public void change(View view){  
        if(binding.getContributor() != null){  
            binding.getContributor().setLogin("zjutkz");  
        }  
    }  

    public void showProgress(){  
        if(dialog == null){  
            dialog = new ProcessDialog(this);  
        }  

        dialog.showMessage("正在加载...");  
    }  

    public void dismissProgress(){  
        if(dialog == null){  
            dialog = new ProcessDialog(this);  
        }  

        dialog.dismiss();  
    }  

    public void getContributors(String owner,String repo){  
        GitHubApi.getContributors(owner, repo)  
                .take(1)  
                .observeOn(AndroidSchedulers.mainThread())  
                .subscribeOn(Schedulers.newThread())  
                .map(new Func1<List<Contributor>, Contributor>() {  

                    @Override  
                    public Contributor call(List<Contributor> contributors) {  
                        return contributors.get(0);  
                    }  
                })  
                .subscribe(contributorSub);  
    }  
} 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

大家有没有发现,activity在MVVM中应该是view层的,但是里面却和MVC一样写了对model的处理。有人会说你可以把对model的处理放到viewmodel层中,这样不是更符合MVVM的设计理念吗?这样确实可以,但是progressDialog的show和dismiss呢?你怎么在viewmodel层中控制?这是view层的东西啊,而且在xml中也没有,我相信会有解决的方案,但是我们有没有一种更加便捷的方式呢?

4.1 MVP + Data Binding

其实,真正的最佳实践都是人想出来的,我们为何不结合一下MVP和MVVM的特点呢?其实谷歌已经做了这样的事,大家可以看下这个。没错,就是MVP+data binding,我们可以使用presenter去做和model层的通信,并且使用data binding去轻松的bind data。还是让我们看代码吧。

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="contributor" type="zjutkz.com.mvpdatabinding.viewmodel.Contributor"/>
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/container"
        android:orientation="vertical"
        android:fitsSystemWindows="true">
        <Button
            android:id="@+id/get"
            android:text="get"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="get"/>
        <Button
            android:id="@+id/change"
            android:text="change"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="change"/>
        <TextView
            android:id="@+id/top_contributor"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:textSize="30sp"
            android:text="@{contributor.login}"/>
    </LinearLayout>
</layout>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
public class MainActivity extends MvpActivity<ContributorView,ContributorPresenter> implements ContributorView {

    private ProcessDialog dialog;

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    }

    @NonNull
    @Override
    public ContributorPresenter createPresenter() {
        return new ContributorPresenter();
    }

    public void get(View view){
        getPresenter().get("square", "retrofit");
    }

    public void change(View view){
        if(binding.getContributor() != null){
            binding.getContributor().setLogin("zjutkz");
        }
    }

    @Override
    public void onLoadContributorStart() {
        showProgress();
    }

    @Override
    public void onLoadContributorComplete(Contributor contributor) {
        binding.setContributor(contributor);
        dismissProgress();
    }

    public void showProgress(){
        if(dialog == null){
            dialog = new ProcessDialog(this);
        }

        dialog.showMessage("正在加载...");
    }

    public void dismissProgress(){
        if(dialog == null){
            dialog = new ProcessDialog(this);
        }

        dialog.dismiss();
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

然后是presenter层

public class ContributorPresenter extends MvpBasePresenter<ContributorView> {

    private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {

        @Override
        public void onStart() {
            ContributorView view = getView();
            if(view != null){
                view.onLoadContributorStart();
            }
        }

        @Override
        public void onCompleted() {

        }

        @Override
        public void onError(Throwable e) {

        }

        @Override
        public void onNext(Contributor topContributor) {
            ContributorView view = getView();
            if(view != null){
                view.onLoadContributorComplete(topContributor);
            }
        }
    };

    public void get(String owner,String repo){
        GitHubApi.getContributors(owner, repo)
                .take(1)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.newThread())
                .map(new Func1<List<Contributor>, Contributor>() {

                    @Override
                    public Contributor call(List<Contributor> contributors) {
                        return contributors.get(0);
                    }
                })
                .subscribe(contributorSub);
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

我们使用了data binding框架去节省了类似findViewById和数据绑定的时间,又使用了presenter去将业务逻辑和view层分离。当然这也不是固定的,你大可以在viewmodel中实现相应的接口,presenter层的数据直接发送到viewmodel中,在viewmodel里更新,因为view和viewmodel是绑定的,这样view也会相应的作出反应。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值