MVC vs. MVP vs. MVVM on Android
在过去的几年里,将Android应用程序转变成逻辑组件的方法已经逐渐成熟。很大程度上摆脱了MVC模式,转而采用更模块化、可测试的模式。
Model View Presenter (MVP) & Model View ViewModel (MVVM)是最广泛被采用的两种替代方案。本文不去讨论哪种方式更适合于Android应用开发,只是通过案例来看到每种模式是如何编写的。
本文通过实现一个井字游戏,分别通过MVC、MVP、MVVM三种模式实现游戏效果。源代码已经上传到Github仓库中。
MVC
Model, View, Controller将应用程序在宏观层面分为3中职责。
Model
Model模型是应用程序中的数据+状态+业务逻辑。可以说是应用程序的大脑,不受View视图和Controller控制器的束缚,因此很多情况下是可以复用的。
View
View视图是Model的展现,负责呈现UI并在用户与应用程序交互时与Controller通信。在MVC架构中,视图通常很“愚蠢”,因为他们不了解底层模型,也没有对状态的理解,或者当用户通过单击按钮,键入值等进行交互时要做什么。这个想法是越少他们知道他们对模型的耦合越松散,因此他们要改变的就越灵活。
Controller
Controller是将app粘在一起的胶水,它是应用程序中的主控制器。当View告诉Controller用户单击按钮时,Controller决定如何与Model进行相应的交互。根据Model中的数据更改, Controller可以根据需要更新View的状态。在Android应用程序中,Controller几乎总是由Activity或Fragment来担任的。
这就是我们的井字游戏中每个类扮演的角色
让我们更详细地检查Controller。
public class MVCTicTacToeActivity extends AppCompatActivity { private static String TAG = MVCTicTacToeActivity.class.getName(); private Board model; private ViewGroup buttonGrid; private View winnerPlayerViewGroup; private TextView winnerPlayerLabel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_mvc_tictactoe); winnerPlayerLabel = (TextView) findViewById(R.id.winnerPlayerLabel); winnerPlayerViewGroup = findViewById(R.id.winnerPlayerViewGroup); buttonGrid = (ViewGroup) findViewById(R.id.buttonGrid); model = new Board(); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_tictactoe, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_reset: reset(); return true; default: return super.onOptionsItemSelected(item); } } public void onCellClicked(View v) { Button button = (Button) v; String tag = button.getTag().toString(); int row = Integer.valueOf(tag.substring(0,1)); int col = Integer.valueOf(tag.substring(1,2)); Log.i(TAG, "Click Row: [" + row + "," + col + "]"); Player playerThatMoved = model.mark(row, col); if(playerThatMoved != null) { button.setText(playerThatMoved.toString()); if (model.getWinner() != null) { winnerPlayerLabel.setText(playerThatMoved.toString()); winnerPlayerViewGroup.setVisibility(View.VISIBLE); } } } private void reset() { winnerPlayerViewGroup.setVisibility(View.GONE); winnerPlayerLabel.setText(""); model.restart(); for( int i = 0; i < buttonGrid.getChildCount(); i++ ) { ((Button) buttonGrid.getChildAt(i)).setText(""); } } }
评估
MVC在分离模型和视图方面做得很好。当然,该模型可以很容易地进行测试,因为它不依赖于任何东西,并且视图在单元测试级别上没有什么可测试的。然而,控制器有一些问题。
Controller关注
- 可测试性 - Controller与Android API紧密联系,难以进行单元测试。
- 模块化和灵活性 - Controller与View紧密耦合。它也可能是View的扩展。如果我们更改View,我们必须返回并更改Controller。
- 维护 - 随着时间的推移,越来越多的代码开始转移到Controller中,使它们变得臃肿和脆弱。
我们如何解决这个问题?MVP来拯救!
MVP
MVP将Controller断开,以便自然View/Activity耦合可以发生,而不会与其他“Controller”责任相关联。让我们再次从MVC对比开始。
Model
与MVC相同/无变化
View
这里唯一的变化是Activity/Fragment现在被视为View的一部分。让Activity实现一个视图界面,以便Presnenter有一个可以编码的界面。这消除了将它耦合到任何特定的页面,并允许使用视图的mock实现进行简单的单元测试。
Presenter
这实质上是MVC的Controller,除了它完全不依赖于View,只是一个接口。这解决了可测试性问题以及我们在MVC中遇到的模块化/灵活性问题。实际上,MVP纯粹主义者认为Presenter不应该对任何Android API或代码有任何引用。
我们再来看看在我们的应用程序中在MVP中的分解。
在下面更详细地看看Presenter,你会注意到的第一件事是每个动作的意图是多么简单和清晰。它不是告诉View如何显示某些东西,而只是告诉它要显示什么。
public class TicTacToePresenter implements Presenter { private TicTacToeView view; private Board model; public TicTacToePresenter(TicTacToeView view){ this.view = view; } @Override public void onCreate() { model = new Board(); } @Override public void onPause() { } @Override public void onResume() { } @Override public void onDestroy() { } public void onButtonSelected(int row, int col) { Player playerThatMoved = model.mark(row, col); if(playerThatMoved != null) { view.setButtonText(row, col, playerThatMoved.toString()); if (model.getWinner() != null) { view.showWinner(playerThatMoved.toString()); } } } public void onResetSelected() { view.clearWinnerDisplay(); view.clearButtons(); model.restart(); } }
为了在不将Activity与Presenter绑定的情况下进行这项工作,我们创建了一个Activity实现的接口。在测试中,我们将基于此接口创建一个mock来测试与Presenter和View的交互。
public interface TicTacToeView { void showWinner(String winnerLabel); void clearWinnerDisplay(); void clearButtons(); void setButtonText(int row,int col,String text); }
评估
我们可以很容易地对Presenter逻辑进行单元测试,因为它没有绑定到任何Android特定的View和API,并且只要View实现*TicTacToeView*界面,我们也可以使用任何其他View。
Presenter的关注
- 维护 - 与Controller一样,Presenter随着时间的推移,倾向于收集额外的业务逻辑。在某些时候,开发者经常会发现自己拥有难以分开的笨重的Presenter。
当然,开发人员可以谨慎的防止这种情况发生。但是,MVVM可以更好的解决这个问题。
MVVM
Android上具有数据绑定功能的 MVVM具有易于测试和模块化的优点,同时还减少了我们必须编写的连接View +Model的代码数量。
我们来看看MVVM的各个部分。
Model
与MVC相同/无变化
View
该View以一种灵活的方式绑定到由viewModel来监控可观察的变量和操作。
ViewModel
ViewModel负责包装Model并准备View所需的可观察数据。它还为View提供了将事件传递给Model的hocks。然而,ViewModel并不依赖于View。
我们的程序早MVVM模式中分解。
让我们仔细看看这里的代码,从ViewModel开始。
public class TicTacToeViewModel implements ViewModel { private Board model; public final ObservableArrayMap<String,String> cells = new ObservableArrayMap<>(); public final ObservableField<String> winner = new ObservableField<>(); public TicTacToeViewModel(){ model = new Board(); } @Override public void onCreate() { } @Override public void onPause() { } @Override public void onResume() { } @Override public void onDestroy() { } public void onResetClick(){ model.restart(); winner.set(null); cells.clear(); } public void onCellClick(int row, int col){ Player player = model.mark(row,col); cells.put(""+row+col,player == null?null:player.toString()); winner.set(model.getWinner()==null?null:model.getWinner().toString()); } }
查看xml文件,看看这些变量和事件是如何绑定的。
<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="player" type="com.shijc.mvx.mvvm.viewmodel.TicTacToeViewModel"/> </data> <LinearLayout android:id="@+id/tictactoe" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical" tools:context="com.acme.tictactoe.view.TicTacToeActivity"> <GridLayout android:id="@+id/buttonGrid" android:layout_width="wrap_content" android:layout_height="wrap_content" android:columnCount="3" android:rowCount="3"> <Button style="@style/tictactoebutton" android:onClick="@{() -> player.onCellClick(0,0)}" android:text='@{player.cells["00"]}' /> <Button style="@style/tictactoebutton" android:onClick="@{() -> player.onCellClick(0,1)}" android:text='@{player.cells["01"]}' /> <Button style="@style/tictactoebutton" android:onClick="@{() -> player.onCellClick(0,2)}" android:text='@{player.cells["02"]}' /> <Button style="@style/tictactoebutton" android:onClick="@{() -> player.onCellClick(1,0)}" android:text='@{player.cells["10"]}' /> <Button style="@style/tictactoebutton" android:onClick="@{() -> player.onCellClick(1,1)}" android:text='@{player.cells["11"]}' /> <Button style="@style/tictactoebutton" android:onClick="@{() -> player.onCellClick(1,2)}" android:text='@{player.cells["12"]}' /> <Button style="@style/tictactoebutton" android:onClick="@{() -> player.onCellClick(2,0)}" android:text='@{player.cells["20"]}' /> <Button style="@style/tictactoebutton" android:onClick="@{() -> player.onCellClick(2,1)}" android:text='@{player.cells["21"]}' /> <Button style="@style/tictactoebutton" android:onClick="@{() -> player.onCellClick(2,2)}" android:text='@{player.cells["22"]}' /> </GridLayout> <LinearLayout android:id="@+id/winnerPlayerViewGroup" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" android:visibility="@{player.winner == null ? View.GONE:View.VISIBLE}" tools:visibility="visible"> <TextView android:id="@+id/winnerPlayerLabel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="20dp" android:textSize="40sp" android:text="@{player.winner}" tools:text="X" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Winner" android:textSize="30sp" /> </LinearLayout> </LinearLayout> </layout>
评估
单元测试现在更容易,因为你真的不依赖于View。测试时,您只需要验证Model更改时是否正确设置了可观察变量。没有必要mock测试View,因为有MVP模式。
MVVM关注
- 维护 - 由于View可以绑定到变量和表达式,因此无关的表示逻辑可能会随着时间的推移而变动,从而有效地将代码添加到我们的XML中。为了避免这种情况,总是直接从ViewModel获取值,而不是试图在视图绑定表达式中计算或派生它们。这样计算可以适当地进行单元测试。
结论
MVP和MVVM在将应用程序分解为模块化单一用途组件方面比MVC做得更好,但它们也增加了应用程序的复杂性。对于只有一个或两个屏幕的非常简单的应用程序,MVC可能工作得很好。带有数据绑定的MVVM具有吸引力,因为它遵循更加反应式的编程模型,并且生成的代码更少。
如果您有兴趣在实践中看到MVP和MVVM的更多示例,我鼓励您查看Google Architecture Blueprints项目。还有很多博客文章深入探讨这几种模式
参考
https://academy.realm.io/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android
本文作者: shijiacheng
本文链接: http://shijiacheng.studio/2018/07/01/mvx/
版权声明: 转载请注明出处!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
腾讯x5webview集成实战
应用中许多网页由于优化的不够理想,出现加载慢,加载时间长等,而且因为碎片化导致兼容性问题,有一些网页有视频内容,产品还提出各种小窗需求,搞得心力憔悴。找到公开的有crosswalk和x5webview,经过分析和研究决定上x5weview,他的好处有哪些呢? 1. TBS(腾讯浏览服务)的优势 1) 速度快:相比系统webview的网页打开速度有30+%的提升; 2) 省流量:使用云端优化技术使流量节省20+%; 3) 更安全:安全问题可以在24小时内修复; 4) 更稳定:经过亿级用户的使用考验,CRASH率低于0.15%; 5) 兼容好:无系统内核的碎片化问题,更少的兼容性问题; 6) 体验优:支持夜间模式、适屏排版、字体设置等浏览增强功能; 7) 功能全:在Html5、ES6上有更完整支持; 8) 更强大:集成强大的视频播放器,支持视频格式远多于系统webview; 9) 视频和文件格式的支持x5内核多于系统内核 10) 防劫持是x5内核的一大亮点 2. 运行环境 1)手机ROM版本高于或等于2.2版本 2)手机RAM大于500M,该RAM值通过手机 /proc/meminfo 文...
- 下一篇
Android自定义无压缩加载超清大图
版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/u013132758。 https://blog.csdn.net/u013132758/article/details/80878256 自定义无压缩加载超清大图 前言 已经很久没有写博客了,前段时间做项目就遇到加载超大图时系统内存溢出,我们一般处理加载图片时OOM的方法都是对图片进行压缩。但是发现手机系统相册是可以打开大图的,今天就分享一波自定义无压缩加载超清大图。 BitmapRegionDecoder BitmapRegionDecoder用来解码一张图片的某个矩形区域,通常用于加载某个图片的指定区域。通过调用该类提供的一系列newInstance(...)方法可获得BitmapRegionDecoder对象,该类提供的主要构造方法如下: 获取该对象后我们可以通过decodeRegion(rect,mOptions)方法传入需要显示的指定区域,就可以得到指定区域的Bitmap。这个方法的第一个参数就是要显示的矩形区域,第二个参数是BitmapFactory.Options(这个 类是Bitma...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS关闭SELinux安全模块
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS8编译安装MySQL8.0.19
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Red5直播服务器,属于Java语言的直播服务器
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS7,8上快速安装Gitea,搭建Git服务器