Android开发模式:MVP Vs MVVM
发布日期:2021-11-12 07:57:12 浏览次数:30 分类:技术文章

本文共 7626 字,大约阅读时间需要 25 分钟。

原文地址:http://www.jianshu.com/p/7ee9bbcb184c

Android常用的开发模式包括MVC,MVP以及MVVM。标准MVC模式不适用于Android的开发,在标准的MVC开发模式中(如网络请求的服务器开发),action(一个URL请求)首先被Controller接收,Controller读取Model的数据,生成View并返回。但是在Android中,Activity/Fragment作为交互的起点,代表的是View而不是Controller,单纯的套用MVC模式会使得Activity/Fragment中混杂Controller层的代码,不利于维护和测试。相比之下,MVP和MVVM更易于实现View层和逻辑代码的分离,本文将通过样例代码对MVP和MVVM两种模式进行讲解。

本文


Demo效果

MVP

MVP包括Model,View和Presenter三部分,通过Presenter层将View和Model隔离开。View和Presenter互相持有对方的引用,可以互相调用。Presenter持有Model的引用,可以调用Model的方法,Model可以通过Presenter的回调函数提醒某个事件的结束,如数据加载成功或失败,交互图如下图所示:


MVP交互图

代码示例:
代码结构如下:


代码结构

在MVP开发模式中,对View的操作都是通过接口(Interface)实现的,对应于Demo中的MvpDemoViewBase:

public interface MvpDemoViewBase {      void updateFirstNameView(String firstName);      void updateLastNameView(String lastName);      void showToastInfo(String toast);}

该接口定义了三个操作View的函数,updateFirstNameView,updateLastNameView和showToastInfo。

作为View的MvpDemoActivity类实现该接口,提供三个函数的具体实现:

public class MvpDemoActivity extends AppCompatActivity implements MvpDemoViewBase {      ...  @Override      public void updateFirstNameView(String firstName) {                         mFirstNameTV.setText("First name: " + firstName);      }      @Override      public void updateLastNameView(String lastName) {                mLastNameTV.setText("Last name: " + lastName);      }      @Override      public void showToastInfo(String toast) {              Toast.makeText(this, toast, Toast.LENGTH_SHORT).show();      }        ...}

在onCreate函数中初始化Presenter:

public class MvpDemoActivity extends AppCompatActivity implements MvpDemoViewBase {        private MvpDemoActivityPresenter mPresenter;        @Override        protected void onCreate(@Nullable Bundle savedInstanceState) {                    ...             mPresenter = new MvpDemoActivityPresenter(this);   }

通过Presenter的引用发起数据请求操作:

@OnClick(R.id.load_button)  protected void onClickLoad(View v) {          mPresenter.loadUserData();  }

Presenter持有View和Model的引用,从Model加载数据,并根据返回数据更新View:

public class MvpDemoActivityPresenter implements MvpLoadDataCallBack {        private MvpDemoViewBase view;      private MvpUserModel userModel;      public MvpDemoActivityPresenter(MvpDemoViewBase view) {                this.view = view;                userModel = new MvpUserModel();      }      // 通过Model加载数据  public void loadUserData() {                userModel.loadUserDataFromNet(this);     }      // 加载数据完成后的回调函数  @Override      public void onLoadSuccess() {           // 通过View更新界面           view.updateFirstNameView(userModel.firstName);              view.updateLastNameView(userModel.lastName);              view.showToastInfo("加载成功");      }      @Override      public void onLoadFail() {}}

Model层实现对数据的定义和加载,并在加载完成后调用Presenter层的回调函数:

public class MvpUserModel {      public String firstName;      public String lastName;      public MvpUserModel() {              this.firstName = "";              this.lastName = "";      }      public void loadUserDataFromNet(MvpLoadDataCallBack callBack) {            // todo: 这里省略了网络请求的过程            this.firstName = "Jack";            this.lastName = "Wang";            // 请求完成调用Presenter层回调函数,通过Presenter层实现对View的更新    callBack.onLoadSuccess();      }}
优点:
1. 三层结构比较清晰    2. 可以在没有View的时候测试Model是否能正常加载数据,只需要写一个实现了View接口的测试类;同理,可以在没有Model的时候通过Presenter层fake数据测试View层是否正常;
缺点:
1. 复杂的页面View层接口可能很多,增加了代码的数量和维护成本

MVVM


MVVM交互图

MVVM通过库将View的元素和Model的属性绑定起来,使得Model数据发生变化时对应的View元素自动更新,底层实现是观察者模式。Data Binding库是一个Support库,支持Android 2.1及以上,Gradle版本1.5.0及以上。
学会了Data Binding库的使用,基本就了解了MVVM的使用。下面通过Demo进行简单介绍。

代码结构:


代码结构

首先在gradle文件中添加如下行启用Data Binding:

android {      ....      dataBinding {            enabled = true      }}

在布局文件mvvm_demo_layout.xml中添加<data>...</data>段定义数据变量:

...

利用import引入Class,利用variable定义变量,type为变量类型,name为变量名,userViewModel和handlers分布代表Model和View,这样就可以在该xml布局文件中使用定义的变量:

...

完成布局文件后,Data Binding库会自动生成一个辅助类MvvmDemoLayoutBind,在MVVMDemoActivity中利用这个辅助类给布局中的变量赋值,并对布局中的元素进行绑定。

public class MvvmDemoActivity extends AppCompatActivity {        private TextView mFirstNameTV;        private TextView mLastNameTV;        private TextView mIsAdultTV;        private MvvmUserViewModel userViewModel;      @Override      protected void onCreate(@Nullable Bundle savedInstanceState) {                    super.onCreate(savedInstanceState);             // 给布局变量赋值           MvvmDemoLayoutBinding binding = DataBindingUtil.setContentView(this, R.layout.mvvm_demo_layout);                  userViewModel = new MvvmUserViewModel();                binding.setUserViewModel(userViewModel);                binding.setHandlers(this);                // 绑定布局元素        mFirstNameTV = binding.firstNameTv;                mLastNameTV = binding.lastNameTv;                mIsAdultTV = binding.isAdultTv;      }  // 定义View响应事件  public void onClickFirstName(View view) {  Toast.makeText(this, "First name is" + mFirstNameTV.getText(), Toast.LENGTH_SHORT).show();}  public void onClickLastName(View v) {  Toast.makeText(this, "Last name is" + mLastNameTV.getText(), Toast.LENGTH_SHORT).show();}  public void onClickLoadData(View v) {  userViewModel.loadUserData();}}

在MvvmUserModel中添加数据的定义和网络加载过程:

public class MvvmUserModel {        public String firstName;        public String lastName;        public boolean isAdult;        public MvvmUserModel() {                firstName = "";                lastName = "";                isAdult = false;        }        public void loadUserDataFromNet(MvvmLoadDataCallBack callBack) {                  // todo: 这里省略了网络请求的过程                this.firstName = "Jack";                this.lastName = "Wang";                this.isAdult = true;                callBack.onLoadSuccess();        }}

最后是作为ViewModel层的MvvmUserViewModel类,负责通过Model层的引用调用数据加载过程,并在回调函数中发起更新界面的消息,Data Binding框架会更新跟数据源绑定的View元素,从而实现界面的自动更新。

public class MvvmUserViewModel extends BaseObservable implements MvvmLoadDataCallBack {        private MvvmUserModel user;        public MvvmUserViewModel() {                user = new MvvmUserModel();        }        @Bindable        public String getFirstName() {                return "First name: " + user.firstName;        }        @Bindable        public String getLastName() {                return "Last name: " + user.lastName;        }        @Bindable        public boolean isAdult() {                return user.isAdult;        }        public void loadUserData() {                user.loadUserDataFromNet(this);        }        @Override        public void onLoadSuccess() {                notifyPropertyChanged(BR.firstName);                        notifyPropertyChanged(BR.lastName);                notifyPropertyChanged(BR.adult);                // todo: 这里单纯的MVVM模式如何展示一条toast变得困难, 必须配合MVP模式添加一个Presenter层才能实现        }        @Override        public void onLoadFail() {        }}

这里使用了Bindable注解,通过给指定的函数添加Bindable注解,Data Binding框架会根据函数名自动生成一个BR的属性,如BR.firstName,在数据源发生变化后,可以调用notifyPropertyChanged(BR.firstName)通知fitstName的变化,getFirstName()返回最新值,更新所有跟firstName数据源绑定的View元素。由于我们需要通过notifyPropertyChanged通知某个或某些数据源的更新,所以MVVM模式中View随Model的更新而更新并不是完全“自动”完成的,而是需要我们“手动”通知的。
同时,并不是所有的数据展示都能通过Data Binding的方式完成,比如最简单的展示一个Toast,或者展示一个数据列表。由于ViewModel层并不持有View层的引用,所以ViewModel层如果想实现Toast或列表的展示,需要借助MVP模式添加一个Presenter层,通过调用Presenter层来实现。这样就不再是单纯的MVVM模式,而是MVVM+MVP了。

优点:
1. 不明显
缺点:
1. 在布局文件xml中加入了很多逻辑代码,违背了展示和逻辑分离的原则,增加了复杂度,难以阅读和维护    2. 单纯的MVVM模式只能实现简单的UI更新,无法实现诸如列表更新的功能,以及加载完成网络数据后弹一个toast之类的功能,必须配合MVP添加一个Presenter实现

综上,我认为MVVM理论意义大于实用意义,而MVP可以适当使用以方便代码维护的测试。

参考:

转载地址:https://blog.csdn.net/happy_love1990/article/details/70217157 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:抓包工具Charles的使用心得(Android、iOS通用)
下一篇:ViewPager撤消左右滑动切换功能

发表评论

最新留言

感谢大佬
[***.8.128.20]2024年03月28日 01时24分13秒