当前位置:   article > 正文

MVC、MVP、MVVM,我到底该怎么选?

玉刚说 mvvm

本文由玉刚说写作平台提供写作赞助

原作者:AndroFarmer

版权声明:本文版权归微信公众号玉刚说所有,未经许可,不得以任何形式转载

前言

MVC、MVP、MVVM是我们工作和面试中都比较重要的一块,但很多时候我们却有点迷惑。比如看了好多篇文章都搞不懂MVC到底是个啥本来想写个MVP写着写着就变成MVC了,到底Databing和MVVM之间有啥见不得人的关系。本篇文章主要从发展的角度来介绍,如mvp,mvvm的出现都是为了解决前者的哪些问题。如果你有同样的疑问,本篇文章可能会给你带来一点收获。但是架构和设计模式相对来说不是那么容易捉摸透的东西,很多需要经过实践才能体会,另外由于本人水平有限,如果写的不对或者不严谨的地方,请不要打我。

MVC

可能由于MVP、MVVM的兴起,MVC在android中的应用变得越来越少了,但MVC是基础,理解好MVC才能更好的理解MVP,MVVM。因为后两种都是基于MVC发展而来的。

1、MVC眼花缭乱设计图

我们从网上搜索mvc相关资料时,如果你多看几篇文章的话可能会发现,好像他们介绍的设计图都不太一样,这里罗列了大部分的设计图

2、MVC设计图解释

到底上面列出的设计图哪个才是对的。其实都是对的。为什么这么说呢,这得从mvc的发展说起。 MVC框架模式最早由Trygve Reenskaug 于1978年在Smalltalk-80系统上首次提出。经过了这么多年的发展,当然会演变出不同的版本,但核心没变依旧还是三层模型Model-View-Control。

3、MVC三层之间的关系

箭头→代表的是一种事件流向,并不一定要持有对方,比如上图中model→view的事件流向,view可以通过注册监听器的形式得到model发来的事件。在设计中model view controller之间如果要通讯,尽量设计成不直接持有,这样方便复用。也符合mvc的设计初衷 在android中三者对应的关系如下:

视图层(View) 对应于xml布局文件和java代码动态view部分

控制层(Controller) MVC中Android的控制层是由Activity来承担的,Activity本来主要是作为初始化页面,展示数据的操作,但是因为XML视图功能太弱,所以Activity既要负责视图的显示又要加入控制逻辑,承担的功能过多。

模型层(Model) 针对业务模型,建立的数据结构和相关的类,它主要负责网络请求,数据库处理,I/O的操作。

由于android中有个god object的存在activity,再加上android中xml布局的功能性太弱,所以activity承担了绝大部分的工作。所以在android中mvc更像是这种形式:

因为activity扮演了controller和view的工作,所以controller和view不太好彻底解耦,但是在一定程度上我们还是可以解耦的。 Talk is cheap. Show me the code. 扯了这么多,我们来看点代码。

4、MVC sample

通过代码来看下,mvc在android中的实现

结构很简单,这里介绍下其中的关键代码

  1. public interface BaseModel {
  2. void onDestroy();
  3. }
  4. 复制代码

BaseModel顾名思义就是所有业务逻辑model的父类,这里的onDestroy()方法用于跟activity或者fragment生命周期同步,在destroy做一些销毁操作

  1. public interface Callback1<T> {
  2. void onCallBack(T t);
  3. }
  4. public interface Callback2<T,P> {
  5. void onCallBack(T t,P p);
  6. }
  7. 复制代码

Callback是根据View或者Controller调用Model时回调的参数个数选择使用

  1. public class SampleModel implements BaseModel{
  2. public void getUserInfo(String uid,Callback1<UserInfo> callback)
  3. {
  4. UserInfo userInfo= new HttpUtil<UserInfo>().get(uid);
  5. callback.onCallBack(userInfo);
  6. }
  7. @Override
  8. public void onDestroy() {
  9. }
  10. public class UserInfo
  11. {
  12. private int age;
  13. private String name;
  14. public int getAge() {
  15. return age;
  16. }
  17. public void setAge(int age) {
  18. this.age = age;
  19. }
  20. public String getName() {
  21. return name;
  22. }
  23. public void setName(String name) {
  24. this.name = name;
  25. }
  26. }
  27. }
  28. 复制代码

SampleModel是我们业务逻辑的具体实现

  1. public class SampleActivity extends AppCompatActivity {
  2. private SampleModel sampleModel;
  3. Button button;
  4. EditText textView;
  5. TextView tvAge,tvName;
  6. @Override
  7. protected void onCreate(Bundle savedInstanceState) {
  8. super.onCreate(savedInstanceState);
  9. setContentView(R.layout.activity_sample);
  10. sampleModel=new SampleModel();
  11. button.setOnClickListener(new View.OnClickListener() {
  12. @Override
  13. public void onClick(View view) {
  14. getUserInfo(textView.getText().toString());
  15. }
  16. });
  17. }
  18. @Override
  19. protected void onDestroy() {
  20. super.onDestroy();
  21. sampleModel.onDestroy();
  22. }
  23. /**
  24. * 获取用户信息
  25. * @param uid
  26. */
  27. private void getUserInfo(String uid)
  28. {
  29. sampleModel.getUserInfo(uid, new Callback1<SampleModel.UserInfo>() {
  30. @Override
  31. public void onCallBack(SampleModel.UserInfo userInfo) {
  32. setDataToView(userInfo);
  33. }
  34. });
  35. }
  36. /**
  37. * 设置用户信息到view
  38. */
  39. private void setDataToView(SampleModel.UserInfo userInfo)
  40. {
  41. tvAge.setText(userInfo.getAge());
  42. tvName.setText(userInfo.getName());
  43. }
  44. }
  45. 复制代码

前面说了Activity充当View和Controller,但是我们依然要区分到底哪一部分是View的操作,哪一部分是Controller的操作。 我们分析下事件的流向
button点击事件的触发:View→Controller 获取用户信息事件的触发:Controller→Model 绑定用户信息到View:Controller→View 至此MVC就讲完了

5、MVC总结

我们这里根据sample来总结下:

  • 具有一定的分层,model彻底解耦,controller和view并没有解耦
  • 层与层之间的交互尽量使用回调或者去使用消息机制去完成,尽量避免直接持有
  • controller和view在android中无法做到彻底分离,但在代码逻辑层面一定要分清
  • 业务逻辑被放置在model层,能够更好的复用和修改增加业务

MVP

1、MVP说明

MVP跟MVC很相像,文章开头列出了很多种MVC的设计图,所以根据MVC的发展来看,我们把MVP当成MVC来看也不为过,因为MVP也是三层,唯一的差别是Model和View之间不进行通讯,都是通过Presenter完成。 前面介绍MVC的时候提到了算是致命缺点吧,在android中由于activity(god object)的存在,Controller和View很难做到完全解耦。但在MVP中就可以很好的解决这个问题 看下MVP的设计图:

一般情况下就这两种

2、MVP Sample

依然延续MVC的例子,修改下结构通过MVP去实现,看下项目代码结构:

callback,http包下内容基本一致,主要看下不同的地方

  1. public interface BasePresenter {
  2. void onDestroy();
  3. }
  4. 复制代码

BasePresenter类似于MVC中的BaseModel,主要负责业务逻辑的实现。我们这里没有把业务逻辑放在Model里去实现,当然把主要业务逻辑放在Model中去实现也是可以的。google的MVP实现方案是把业务逻辑放在presenter中,弱化Model,我们这里也是这样做的。

  1. public interface BaseView<P extends BasePresenter> {
  2. void setPresenter(P presenter);
  3. }
  4. 复制代码

BaseView是所有View的父类,将android中的view抽象话出来,只有跟view相关的操作都由baseView的实现类去完成。

  1. public class SampleContract {
  2. public static class Presenter implements BasePresenter
  3. {
  4. public void getUserInfo(String uid,Callback1<SampleModel.UserInfo> callback)
  5. {
  6. SampleModel.UserInfo userInfo= new HttpUtil<SampleModel.UserInfo>().get(uid);
  7. callback.onCallBack(userInfo);
  8. }
  9. @Override
  10. public void onDestroy() {
  11. }
  12. }
  13. public interface View extends BaseView<Presenter>
  14. {
  15. void setDataToView(SampleModel.UserInfo userInfo);
  16. }
  17. }
  18. 复制代码

Contract 契约类这是Google MVP与其他实现方式的又一个不同,契约类用于定义同一个界面的view的接口和presenter的具体实现。好处是通过规范的方法命名和注释可以清晰的看到整个页面的逻辑。

  1. public class SampleActivity extends AppCompatActivity implements SampleContract.View{
  2. private SampleContract.Presenter mPresenter;
  3. Button button;
  4. EditText textView;
  5. TextView tvAge,tvName;
  6. @Override
  7. protected void onCreate(Bundle savedInstanceState) {
  8. super.onCreate(savedInstanceState);
  9. setContentView(R.layout.activity_sample);
  10. setPresenter(new SampleContract.Presenter());
  11. button.setOnClickListener(new View.OnClickListener() {
  12. @Override
  13. public void onClick(View view) {
  14. mPresenter.getUserInfo(textView.getText().toString(), new Callback1<SampleModel.UserInfo>() {
  15. @Override
  16. public void onCallBack(SampleModel.UserInfo userInfo) {
  17. setDataToView(userInfo);
  18. }
  19. });
  20. }
  21. });
  22. }
  23. @Override
  24. protected void onDestroy() {
  25. super.onDestroy();
  26. mPresenter.onDestroy();
  27. }
  28. @Override
  29. public void setDataToView(SampleModel.UserInfo userInfo) {
  30. tvAge.setText(userInfo.getAge());
  31. tvName.setText(userInfo.getName());
  32. }
  33. @Override
  34. public void setPresenter(SampleContract.Presenter presenter) {
  35. mPresenter=presenter;
  36. }
  37. }
  38. 复制代码

这里的SampleActivity实现了SampleContract.View只是作为View存在的。虽然看起来,跟MVC中的实现很相似,但却有本质的区别。mPresenter为Model和View之间交互的桥梁。Presenter跟View相互持有,这里SampleActivity实现了SampleContract.View,mPresenter作为SampleActivity的成员变量,SampleActivity当然持有mPresenter,由于mPresenter是非静态的成员标量,因此默认持有SampleActivity的引用。

3、MVP总结

通过引入接口BaseView,让相应的视图组件如Activity,Fragment去实现BaseView,实现了视图层的独立,通过中间层Preseter实现了Model和View的完全解耦。MVP彻底解决了MVC中View和Controller傻傻分不清楚的问题,但是随着业务逻辑的增加,一个页面可能会非常复杂,UI的改变是非常多,会有非常多的case,这样就会造成View的接口会很庞大。

MVVM

1、MVVM说明

MVP中我们说过随着业务逻辑的增加,UI的改变多的情况下,会有非常多的跟UI相关的case,这样就会造成View的接口会很庞大。而MVVM就解决了这个问题,通过双向绑定的机制,实现数据和UI内容,只要想改其中一方,另一方都能够及时更新的一种设计理念,这样就省去了很多在View层中写很多case的情况,只需要改变数据就行。 先看下MVVM设计图:

一般情况下就这两种情况,这看起来跟MVP好像没啥差别,其实区别还是挺大的,在MVP中View和presenter要相互持有,方便调用对方,而在MVP中 View和ViewModel通过Binding进行关联,他们之前的关联处理通过DataBinding完成。

2、MVVM与DataBinding的关系

一句话表述就是,MVVM是一种思想,DataBinding是谷歌推出的方便实现MVVM的工具。在google推出DataBinding之前,因为xml layout功能较弱,想实现MVVM非常困难。而DataBinding的出现可以让我们很方便的实现MVVM。

3、DataBinding简介

DataBinding是实现视图和数据双向绑定的工具,这里简单介绍下基本用法,详细用法可以参照官方:https://developer.android.com/topic/libraries/data-binding/ 启用DataBinding,只需要在gradle文件中添加如下代码:

  1. android {
  2. dataBinding{
  3. enabled true
  4. }
  5. }
  6. 复制代码

通过DataBindingUtil可以动态生成一个ViewDataBinding的子类,类名以layout文件名大写加Binding组成,如:

  1. ActivitySampleMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_sample_mvvm);
  2. 复制代码

在layout中需要我们配置,每个控件绑定的实体对象,以layout进行包裹,data中配置变量名和类型,通过@{}或@={}的方式进行引用,其中@={}的方式表示双向绑定。目前支持双向绑定的控件如下:

AbsListView android:selectedItemPosition CalendarView android:date CompoundButton android:checked DatePicker android:year, android:month, android:day NumberPicker android:value RadioGroup android:checkedButton RatingBar android:rating SeekBar android:progress TabHost android:currentTab TextView android:text TimePicker android:hour, android:minute

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android">
  3. <data >
  4. <variable
  5. name="user"
  6. type="com.androfarmer.mvvm.model.SampleModel.UserInfo">
  7. </variable>
  8. </data>
  9. <LinearLayout
  10. android:layout_width="match_parent"
  11. android:layout_height="match_parent"
  12. android:orientation="vertical">
  13. <TextView
  14. android:id="@+id/tv_name"
  15. android:layout_width="wrap_content"
  16. android:layout_height="wrap_content"
  17. android:text="@={user.name}"
  18. />
  19. <TextView
  20. android:id="@+id/tv_age"
  21. android:layout_width="wrap_content"
  22. android:layout_height="wrap_content"
  23. android:text="@={user.age+``}"
  24. />
  25. </LinearLayout>
  26. </layout>
  27. 复制代码

以上为具体在xml的用法展示

  1. public static class UserInfo extends BaseObservable
  2. {
  3. private int age;
  4. private String name;
  5. @Bindable
  6. public int getAge() {
  7. return age;
  8. }
  9. public void setAge(int age) {
  10. this.age = age;
  11. notifyPropertyChanged(BR.age);
  12. }
  13. @Bindable
  14. public String getName() {
  15. return name;
  16. }
  17. public void setName(String name) {
  18. this.name = name;
  19. notifyPropertyChanged(BR.name);
  20. }
  21. }
  22. 复制代码

为了实现双向绑定还需要对数据实体类做处理,继承BaseObservable,对读写方法做@Bindable和notifyPropertyChanged处理。还可以直接使用官方提供的泛型可观察对象 ObservableField,比如: private ObservableField name=new ObservableField<>();

4、MVVM Sample

MVVM中跟MVP中一样,将三层划分的很清楚,Activity和xml layout充当View,ViewModel处理业务逻辑以及获取数据,弱化Model。 很多代码跟前面类似,这里只列出核心代码,ViewModel层的

  1. public interface BaseViewModel {
  2. void onDestroy();
  3. }
  4. public abstract class AbstractViewModel<T extends ViewDataBinding> implements BaseViewModel {
  5. public T mViewDataBinding;
  6. public AbstractViewModel(T viewDataBinding)
  7. {
  8. this.mViewDataBinding=viewDataBinding;
  9. }
  10. @Override
  11. public void onDestroy() {
  12. mViewDataBinding.unbind();
  13. }
  14. }
  15. public class SampleViewModel extends AbstractViewModel<ActivitySampleMvvmBinding> {
  16. public SampleViewModel(ActivitySampleMvvmBinding viewDataBinding) {
  17. super(viewDataBinding);
  18. }
  19. public void getUserInfo(String uid, Callback1<SampleModel.UserInfo> callback)
  20. {
  21. //从网络或缓存获取信息
  22. SampleModel.UserInfo userInfo=new SampleModel.UserInfo();
  23. userInfo.setName("tom");
  24. userInfo.setAge(18);
  25. callback.onCallBack(userInfo);
  26. }
  27. }
  28. 复制代码

ViewMode层主要处理业务逻辑和获取数据,mViewDataBinding是通过View层传递过来。

  1. private SampleViewModel mSampleViewModel;
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. ActivitySampleMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_sample_mvvm);
  6. mSampleViewModel=new SampleViewModel(binding);
  7. setDataToView();
  8. }
  9. private void setDataToView()
  10. {
  11. mSampleViewModel.getUserInfo("uid", new Callback1<SampleModel.UserInfo>() {
  12. @Override
  13. public void onCallBack(SampleModel.UserInfo userInfo) {
  14. mSampleViewModel.mViewDataBinding.setUser(userInfo);
  15. }
  16. });
  17. }
  18. 复制代码

xml layout代码在上面介绍databing的用法时已给出,通过上面代码我们就将数据UserInfo跟View进行绑定了。比如我们更新用户信息,可以直接对View上的属性进行修改: mSampleViewModel.mViewDataBinding.tvName.setText("rose"); 也可以通过修改UserInfo实体类的字段信息: mSampleViewModel.mViewDataBinding.setUser(userInfo);

从此告别MVP中View层好多接口的问题,让View变得更简洁,修改任何一方,两者都会保持数据同步。

5、MVVM 总结

看起来MVVM很好的解决了MVC和MVP的不足,但是由于数据和视图的双向绑定,导致出现问题时不太好定位来源,有可能数据问题导致,也有可能业务逻辑中对视图属性的修改导致。如果项目中打算用MVVM的话可以考虑使用官方的架构组件ViewModel、LiveData、DataBinding去实现MVVM

关于MVC,MVP,MVVM如何选择的探讨

前面在介绍MVC、MVP、MVVM时并没有去详细列出他们的优缺点,主要原因有两个:

  1. 网上这方面总结的还是挺多的
  2. 其实关于架构,设计,模块化等等,它们的优缺点没有绝对的,主要看实现者如何去做

比如在mvp中我们要实现根据业务逻辑和页面逻辑做很多Present和View的具体实现,如果这些case太多,会导致代码的可读性变差。但是通过引入contract契约类,会让业务逻辑变得清晰许多。因此不管是用哪种设计模式,只要运用得当,都可以达到想要的结果。 如果非要说怎么选的话,以我浅薄的知识建议如下:

  1. 如果项目简单,没什么复杂性,未来改动也不大的话,那就不要用设计模式或者架构方法,只需要将每个模块封装好,方便调用即可,不要为了使用设计模式或架构方法而使用。
  2. 对于偏向展示型的app,绝大多数业务逻辑都在后端,app主要功能就是展示数据,交互等,建议使用mvvm。
  3. 对于工具类或者需要写很多业务逻辑app,使用mvp或者mvvm都可。
  4. 如果想通过一个项目去学习架构和设计模式,建议用MVC然后在此基础上慢慢挖掘改进。最后你可能发现,改进的最终结果可能就变成了mvp,mvvm。

PS:代码部分很多只是为了演示具体设计模式原理,部分为伪代码,还有些代码写的不是那么严谨。本篇文章参考如下:

http://www.voidcn.com/article/p-ssodjasa-brk.html https://www.jianshu.com/p/4830912f5162

欢迎关注我的微信公众号,接收第一手技术干货

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/66186
推荐阅读
相关标签
  

闽ICP备14008679号