Android开发模式:MVP Vs MVVM
vggt6207
8年前
<p style="text-align:center"><img src="https://simg.open-open.com/show/fd1d6e4352908e6893ca66dc4a1cc1f2.jpg"></p> <p style="text-align:center">开发模式</p> <p>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两种模式进行讲解。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/065d72f009d97c709aaa204df830c9e9.gif"></p> <p style="text-align:center">Demo效果</p> <p><strong>MVP</strong></p> <p>MVP包括Model,View和Presenter三部分,通过Presenter层将View和Model隔离开。View和Presenter互相持有对方的引用,可以互相调用。Presenter持有Model的引用,可以调用Model的方法,Model可以通过Presenter的回调函数提醒某个事件的结束,如数据加载成功或失败,交互图如下图所示:</p> <p><img src="https://simg.open-open.com/show/376c4f68e5827b9b1f1a465560cfff4b.png"></p> <p style="text-align:center">MVP交互图</p> <p>代码示例:</p> <p>代码结构如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/acfc96d268e5ccddd6d64122b9f85413.png"></p> <p style="text-align:center">代码结构</p> <p>在MVP开发模式中,对View的操作都是通过接口(Interface)实现的,对应于Demo中的MvpDemoViewBase:</p> <pre> <code class="language-java">public interface MvpDemoViewBase { void updateFirstNameView(String firstName); void updateLastNameView(String lastName); void showToastInfo(String toast); }</code></pre> <p>该接口定义了三个操作View的函数,updateFirstNameView,updateLastNameView和showToastInfo。</p> <p>作为View的MvpDemoActivity类实现该接口,提供三个函数的具体实现:</p> <pre> <code class="language-java">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(); } ... }</code></pre> <p>在onCreate函数中初始化Presenter:</p> <pre> <code class="language-java">public class MvpDemoActivity extends AppCompatActivity implements MvpDemoViewBase { private MvpDemoActivityPresenter mPresenter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { ... mPresenter = new MvpDemoActivityPresenter(this); }</code></pre> <p>通过Presenter的引用发起数据请求操作:</p> <pre> <code class="language-java">@OnClick(R.id.load_button)protected void onClickLoad(View v) { mPresenter.loadUserData(); }</code></pre> <p>Presenter持有View和Model的引用,从Model加载数据,并根据返回数据更新View:</p> <pre> <code class="language-java">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() {} }</code></pre> <p>Model层实现对数据的定义和加载,并在加载完成后调用Presenter层的回调函数:</p> <pre> <code class="language-java">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(); } }</code></pre> <p><strong>优点:</strong></p> <pre> <code class="language-java">1. 三层结构比较清晰 2. 可以在没有View的时候测试Model是否能正常加载数据,只需要写一个实现了View接口的测试类;同理,可以在没有Model的时候通过Presenter层fake数据测试View层是否正常;</code></pre> <p><strong>缺点:</strong></p> <pre> <code class="language-java">1. 复杂的页面View层接口可能很多,增加了代码的数量和维护成本</code></pre> <p><strong>MVVM</strong></p> <p><img src="https://simg.open-open.com/show/97b01f8ed8942207f012ed4d35088ee0.png"></p> <p style="text-align:center">MVVM交互图</p> <p>MVVM通过 Data Binding 库将View的元素和Model的属性绑定起来,使得Model数据发生变化时对应的View元素自动更新,底层实现是观察者模式。Data Binding库是一个Support库,支持Android 2.1及以上,Gradle版本1.5.0及以上。</p> <p>学会了Data Binding库的使用,基本就了解了MVVM的使用。下面通过Demo进行简单介绍。</p> <p>代码结构:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/27f9e6b3eda41ef5a9233b23e66520d5.png"></p> <p style="text-align:center">代码结构</p> <p>首先在gradle文件中添加如下行启用Data Binding:</p> <pre> <code class="language-java">android { .... dataBinding { enabled = true } }</code></pre> <p>在布局文件mvvm_demo_layout.xml中添加<data>...</data>段定义数据变量:</p> <pre> <code class="language-java"><layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data> <import type="android.view.View" /> <variable name="userViewModel" type="com.magic.wangdongliang.designpatterndemo.mvvm.viewmodel.MvvmUserViewModel" /> <variable name="handlers" type="com.magic.wangdongliang.designpatterndemo.mvvm.view.MvvmDemoActivity" /> </data> ... </layout></code></pre> <p>利用import引入Class,利用variable定义变量,type为变量类型,name为变量名,userViewModel和handlers分布代表Model和View,这样就可以在该xml布局文件中使用定义的变量:</p> <pre> <code class="language-java"><layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> ... <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/first_name_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="30dp" android:text="@{userViewModel.firstName}" tools:text="First name: "/> <TextView android:id="@+id/last_name_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="30dp" android:layout_marginTop="30dp" android:text="@{userViewModel.lastName}" tools:text="Last name: "/> <TextView android:id="@+id/is_adult_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="30dp" android:layout_marginTop="30dp" android:text="Is adult: Yes" android:visibility="@{userViewModel.isAdult ? View.VISIBLE : View.GONE}" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="加载数据" android:layout_marginTop="30dp" android:layout_gravity="center_horizontal" android:onClick="@{handlers.onClickLoadData}"/> </LinearLayout> </layout></code></pre> <p>完成布局文件后,Data Binding库会自动生成一个辅助类MvvmDemoLayoutBind,在MVVMDemoActivity中利用这个辅助类给布局中的变量赋值,并对布局中的元素进行绑定。</p> <pre> <code class="language-java">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();} }</code></pre> <p>在MvvmUserModel中添加数据的定义和网络加载过程:</p> <pre> <code class="language-java">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(); } }</code></pre> <p>最后是作为ViewModel层的MvvmUserViewModel类,负责通过Model层的引用调用数据加载过程,并在回调函数中发起更新界面的消息,Data Binding框架会更新跟数据源绑定的View元素,从而实现界面的自动更新。</p> <pre> <code class="language-java">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() { }}</code></pre> <p>这里使用了Bindable注解,通过给指定的函数添加Bindable注解,Data Binding框架会根据函数名自动生成一个BR的属性,如BR.firstName,在数据源发生变化后,可以调用notifyPropertyChanged(BR.firstName)通知fitstName的变化,getFirstName()返回最新值,更新所有跟firstName数据源绑定的View元素。由于我们需要通过notifyPropertyChanged通知某个或某些数据源的更新,所以MVVM模式中View随Model的更新而更新并不是完全“自动”完成的,而是需要我们“手动”通知的。</p> <p>同时,并不是所有的数据展示都能通过Data Binding的方式完成,比如最简单的展示一个Toast,或者展示一个数据列表。由于ViewModel层并不持有View层的引用,所以ViewModel层如果想实现Toast或列表的展示,需要借助MVP模式添加一个Presenter层,通过调用Presenter层来实现。这样就不再是单纯的MVVM模式,而是MVVM+MVP了。</p> <p><strong>优点:</strong></p> <pre> <code class="language-java">1. 不明显</code></pre> <p><strong>缺点:</strong></p> <pre> <code class="language-java">1. 在布局文件xml中加入了很多逻辑代码,违背了展示和逻辑分离的原则,增加了复杂度,难以阅读和维护 2. 单纯的MVVM模式只能实现简单的UI更新,无法实现诸如列表更新的功能,以及加载完成网络数据后弹一个toast之类的功能,必须配合MVP添加一个Presenter实现</code></pre> <p>综上,我认为MVVM理论意义大于实用意义,而MVP可以适当使用以方便代码维护的测试。</p> <p><strong>参考:</strong></p> <p><a href="/misc/goto?guid=4959670548164816075" rel="nofollow,noindex">http://stackoverflow.com/questions/2056/what-are-mvp-and-mvc-and-what-is-the-difference</a></p> <p><a href="/misc/goto?guid=4959633263730604018" rel="nofollow,noindex">https://developer.android.com/tools/data-binding/guide.html</a></p> <p> </p> <p>来自:http://www.jianshu.com/p/7ee9bbcb184c</p> <p> </p>