ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

MVC、MVP 和 MVVM 架构模式 & ViewModel

2021-06-22 16:29:27  阅读:218  来源: 互联网

标签:MVP MVVM ViewModel Presenter Activity public View


在学习viewModel之前,我们需要先了解MVC、MVP 和 MVVM 架构模式。

简单的了解可以查看MVC,MVP 和 MVVM 的图示

另超级好文:Android App的设计架构:MVC,MVP,MVVM与架构

MVC

图示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Hwjf3B8-1624350380367)(imgs/15226743-86c2d4be3b5833c3.webp)]

是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

Android中界面部分也采用了当前比较流行的MVC框架,在Android中:

视图层(View) :一般采用XML文件进行界面的描述。

控制层(Controller): Android的控制层的重任通常落在了众多的Activity的肩上。这句话也就暗含了不要在Activity中写代码,要通过Activity交割Model业务逻辑层处理,这样做的另外一个原因是Android中的Actiivity的响应时间是5s,如果耗时的操作放在这里,程序就很容易被回收掉。

模型层(Model) :我们针对业务模型,建立的数据结构和相关的类,就可以理解为AndroidApp的Model,Model是与View无关,而与业务相关的。对数据库的操作、对网络等的操作都应该在Model里面处理,当然对业务计算等操作也是必须放在的该层的。

MVC的缺点: 在Android开发中,Activity并不是一个标准的MVC模式中的Controller,它的首要职责是加载应用的布局和初始化用户 界面,并接受并处理来自用户的操作请求,进而作出响应。随着界面及其逻辑的复杂度不断提升,Activity类的职责不断增加,以致变得庞大臃肿

MVP

在App开发过程中,经常出现的问题就是某一部分的代码量过大,虽然做了模块划分和接口隔离,但也很难完全避免。因为Activity本身需要担负与用户之间的操作交互,界面的展示,不是单纯的Controller或View。因此引入MVP框架。MVP图示如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P0EukdXv-1624350380369)(imgs/15226743-947a7c01f8199148.webp)]

MVP从更早的MVC框架演变过来,与MVC有一定的相似性:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。

View:负责绘制UI元素(一般包括Activity,Fragment,Adapter等直接和UI相关的类)、与用户进行交互(在Android中体现为Activity)

Model:负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合)

Presenter:作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。

View interface: 需要View实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试

View interface的必要性:回想一下你在开发Android应用时是如何对代码逻辑进行单元测试的?是否每次都要将应用部署到Android模拟器或真机上,然后通过模拟用 户操作进行测试?然而由于Android平台的特性,每次部署都耗费了大量的时间,这直接导致开发效率的降低。而在MVP模式中,处理复杂逻辑的Presenter是通过interface与View(Activity)进行交互的,这说明我们可以通过自定义类实现这个interface来模拟Activity的行为对Presenter进行单元测试,省去了大量的部署及测试的时间。

当我们将Activity复杂的逻辑处理移至另外的一个类(Presenter)中时,Activity其实就是MVP模式中的View,它负责UI元素的初始化,建立UI元素与Presenter的关联(Listener之类),同时自己也会处理一些简单的逻辑(复杂的逻辑交由 Presenter处理)。

MVP的Presenter是框架的控制者,承担了大量的逻辑操作,而MVC的Controller更多时候承担一种转发的作用。因此在App中引入MVP的原因,是为了将此前在Activty中包含的大量逻辑操作放到控制层中,避免Activity的臃肿。

两种模式的主要区别:

(最主要区别)View与Model并不直接交互,而是通过与Presenter交互来与Model间接交互。而在MVC中View可以与Model直接交互
通常View与Presenter是一对一的,但复杂的View可能绑定多个Presenter来处理逻辑。而Controller是基于行为的,并且可以被多个View共享,Controller可以负责决定显示哪个View
Presenter与View的交互是通过接口来进行的,更有利于添加单元测试。

因此我们可以发现MVP的优点如下:

1、模型与视图完全分离,我们可以修改视图而不影响模型;

2、可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部;

3、我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁;

4、如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)。

在MVP中,Activity的代码不臃肿;
在MVP中,Model(IUserModel的实现类)的改动不会影响Activity(View),两者也互不干涉,而在MVC中会;
在MVP中,IUserView这个接口可以实现方便地对Presenter的测试;
在MVP中,UserPresenter可以用于多个视图,但是在MVC中的Activity就不行。

MVVM

MVVM可以算是MVP的升级版,其中的VM是ViewModel的缩写,ViewModel可以理解成是View的数据模型和Presenter的合体,ViewModel和View之间的交互通过Data Binding完成,而Data Binding可以实现双向的交互,这就使得视图和控制层之间的耦合程度进一步降低,关注点分离更为彻底,同时减轻了Activity的压力。其图示如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ybdwYDhL-1624350380372)(imgs/15226743-1b2adc4a66e12c6e.webp)]

MVVM模式将Presener改名为View Model,基本上与MVP模式完全一致,唯一的区别是,它采用双向绑定(data-binding),View的变动,自动反映在 ViewModel,反之亦然。

所以这里也就出现了ViewModel。

ViewModel

在Android中的MVVM出现是Jetpack系列,主要使用LiveData、ViewModel和Data-binding结合实现MVVM。

我们知道类似旋转屏幕等配置项改变会导致我们的 Activity 被销毁并重建,此时 Activity 持有的数据就会跟随着丢失,而ViewModel 则并不会被销毁,从而能够帮助我们在这个过程中保存数据,而不是在 Activity 重建后重新去获取。并且 ViewModel 能够让我们不必去担心潜在的内存泄露问题,同时 ViewModel 相比于用onSaveInstanceState() 方法更有优势,比如存储相对大的数据,并且不需要序列化以及反序列化。

ViewModel的生命周期图为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TmYwSpkb-1624350380375)(imgs/43n176wgwv.png)]

从上图中可以看到 ViewModel 在 Activity 的重建时依然存活。为什么?

使用ViewModel主要的业务场景为:

我们旋转屏幕而没有其他处理的时候,因为屏幕旋转activity被销毁重建,存放在activity的数据自然就会丢失了。而如果使用ViewModel就不会出现这种情况。

比如新建一个MyViewModel.java,内容如下:

public class MyViewModel extends ViewModel {
    public int number = 0;
}

在主Activity中的代码如下:

public class MainActivity extends AppCompatActivity {
    Button button;
    MyViewModel myViewModel;
    TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = findViewById(R.id.button);
        textView = findViewById(R.id.textView);
        myViewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class);
        textView.setText(String.valueOf(myViewModel.number));
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myViewModel.number++;
                textView.setText(String.valueOf(myViewModel.number));
            }
        });
    }
}

就可以实现在屏幕翻转的时候,数据不会丢失。

那么ViewModel是如何实现的?

ViewModel内部实现

and ViewModel怎么更新数据的。

myViewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class);

通过ViewModelProvider实例的get方法来得到自己所需要的ViewModel实例对象。

那么myViewModel.number++;操作的时候,是如何完成更新和保存的?不妨先看下我们的ViewModel对象是如何得到的:

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    }
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }

    viewModel = mFactory.create(modelClass);
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}

在viewModel实例化后,使用ViewModelStore来进行存储。当首次创建的时候使用NewInstanceFactory,如下:

public static class NewInstanceFactory implements Factory {

    @SuppressWarnings("ClassNewInstance")
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        //noinspection TryWithIdenticalCatches
        try {
            return modelClass.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        }
    }
}

可以看到这里是反射来得到的实例对象。然后通过mViewModelStore.put(key, viewModel);将该对象进行存储,put实现如下:

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

在ViewModelStore类中,使用HashMap进行存储,key为前面提到的DEFAULT_KEY + ":" + canonicalName,也就是DEFAULT_KEY + ":"加全限定类名。

到此,我们知道了ViewModel是如何得到实例对象,以及如何存储的了。但是还是不知道该对象是如何进行更新数据的。所以我们需要更加仔细些。

我们重新观察:

new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class);

前面就只剩下ViewModelProvider类的这个构造方法没有了解,那么不妨看下:

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    this(owner.getViewModelStore(), factory);
}

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    this.mViewModelStore = store;
}

也就是说关键在于ViewModelStore是如何得到的。也即是说ViewModelStoreOwner类是如何书写的。注意到:

public interface ViewModelStoreOwner {
    ViewModelStore getViewModelStore();
}

ViewModelStoreOwner是一个接口,且传入的是this对象,那么在Activity的某个父类中必然做了相应的实现。很容易可以追踪到在FragmentActivity中实现了这个接口:

public class FragmentActivity extends SupportActivity implements ViewModelStoreOwner

那么我们可以找下这个接口对应的getViewModelStore方法的实现:

public ViewModelStore getViewModelStore() {
    if (this.getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.");
    } else {
        if (this.mViewModelStore == null) {
            FragmentActivity.NonConfigurationInstances nc = (FragmentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
            if (nc != null) {
                this.mViewModelStore = nc.viewModelStore;
            }

            if (this.mViewModelStore == null) {
                this.mViewModelStore = new ViewModelStore();
            }
        }

        return this.mViewModelStore;
    }
}

不存在就使用ViewModelStore的无参数构造进行实例化。

一件比较有趣的事情就是,我在追踪viewModel类的时候,发现了这里给了一个使用案例,和我使用的并不一样:

public class MyFragment extends Fragment {
    public void onStart() {
    UserModel userModel = 	ViewModelProviders.of(getActivity()).get(UserModel.class);
    }
}

使用的是ViewModelProviders,而不是ViewModelProvider。然后我在MainActivity.java文件中做了相应的替换:

//        myViewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class);
myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);

发现结果居然一样。

注意到我这里的版本为:implementation 'android.arch.lifecycle:extensions:1.1.1',原来在2.2.0中ViewModelProviders方法被弃用了。

但是,至于ViewModel怎么更新数据的?这个我确实没有看见。这个问题就以后再思考。

标签:MVP,MVVM,ViewModel,Presenter,Activity,public,View
来源: https://blog.csdn.net/qq_26460841/article/details/118109993

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有