MVC vs. MVP vs. MVVM on Android

简介: 在过去的几年里,将Android应用程序转变成逻辑组件的方法已经逐渐成熟。很大程度上摆脱了MVC模式,转而采用更模块化、可测试的模式。Model View Presenter (MVP) & Model View ViewModel (MVVM)是最广泛被采用的两种替代方案。

在过去的几年里,将Android应用程序转变成逻辑组件的方法已经逐渐成熟。很大程度上摆脱了MVC模式,转而采用更模块化、可测试的模式。

Model View Presenter (MVP) & Model View ViewModel (MVVM)是最广泛被采用的两种替代方案。本文不去讨论哪种方式更适合于Android应用开发,只是通过案例来看到每种模式是如何编写的。

本文通过实现一个井字游戏,分别通过MVC、MVP、MVVM三种模式实现游戏效果。源代码已经上传到Github仓库中。

img_69a044b74803f2e446632872994039b2.png
井字游戏效果图.png

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来担任的。

这就是我们的井字游戏中每个类扮演的角色

img_63d6fab362c0e2ad50b3980e2be4431e.png
mvc模型角色图.png

让我们更详细地检查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中的分解。

img_b65b02b8dfa597a1fd336f7ada6edf20.png
mvp模型角色图.png

在下面更详细地看看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模式中分解。

img_48e8e6a401857c7c794af5788627b66c.png
mvvm模型角色图.png

让我们仔细看看这里的代码,从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/
版权声明: 转载请注明出处!

目录
相关文章
|
29天前
|
安全 Android开发 iOS开发
Android vs iOS:深入剖析两大移动操作系统的优劣与未来趋势####
【10月更文挑战第21天】 本文旨在通过技术视角,全面对比分析Android与iOS两大主流移动操作系统的架构差异、用户体验、安全性及生态系统等方面,探讨其各自优势与不足,并预测未来发展趋势。 ####
40 1
|
1月前
|
前端开发 JavaScript 测试技术
android做中大型项目完美的架构模式是什么?是MVVM吗?如果不是,是什么?
在 Android 开发中,选择合适的架构模式对于构建中大型项目至关重要。常见的架构模式有 MVVM、MVP、MVI、Clean Architecture 和 Flux/Redux。每种模式都有其优缺点和适用场景,例如 MVVM 适用于复杂 UI 状态和频繁更新,而 Clean Architecture 适合大型项目和多平台开发。选择合适的架构应考虑项目需求、团队熟悉度和可维护性。
55 6
|
1月前
|
搜索推荐 Android开发 iOS开发
安卓vs. iOS:操作系统的终极对决####
本文探讨了安卓和iOS两种主流移动操作系统的特点,从用户体验、系统生态、硬件兼容性等维度进行对比分析。通过深入浅出的方式,揭示了两者在技术层面的优劣及未来发展趋势,旨在帮助用户更好地理解并选择适合自己的平台。 ####
|
1月前
|
安全 搜索推荐 Android开发
Android vs. iOS:解锁智能手机操作系统的奥秘####
【10月更文挑战第21天】 在当今这个数字化时代,智能手机已成为我们生活中不可或缺的伙伴。本文旨在深入浅出地探讨两大主流操作系统——Android与iOS的核心差异、优势及未来趋势,帮助读者更好地理解这两个平台背后的技术哲学和用户体验设计。通过对比分析,揭示它们如何塑造了我们的数字生活方式,并展望未来可能的发展路径。无论您是技术爱好者还是普通用户,这篇文章都将带您走进一个充满创新与可能性的移动世界。 ####
77 3
|
2月前
|
前端开发
MVVM是什么?和MVC有何区别呢?
【10月更文挑战第11天】MVVM 和 MVC 都是为了更好地组织和管理软件架构,提高开发效率和代码质量。理解它们的特点和区别,有助于我们在实际开发中做出更合适的选择,并构建出更加优秀的应用程序。
|
1月前
|
安全 搜索推荐 Android开发
Android vs. iOS:一场永无止境的较量####
在智能手机操作系统领域,Android与iOS犹如两极,各自引领着不同的技术潮流和用户体验哲学。本文深入探讨了这两个平台的发展历程、核心优势、以及它们如何塑造了我们的数字生活,旨在为读者提供一个全面而客观的视角,理解这场持续多年的“战争”背后的真正意义。 ####
|
2月前
|
存储 前端开发 测试技术
MVC、MVP、MVVM 模式
MVC、MVP 和 MVVM 是三种常见的软件架构模式,用于分离用户界面和业务逻辑。MVC(Model-View-Controller)通过模型、视图和控制器分离数据、界面和控制逻辑;MVP(Model-View-Presenter)将控制逻辑移到 Presenter 中,减少视图的负担;MVVM(Model-View-ViewModel)通过数据绑定机制进一步解耦视图和模型,提高代码的可维护性和测试性。
|
2月前
|
安全 Android开发 iOS开发
Android vs iOS:探索移动操作系统的设计与功能差异###
【10月更文挑战第20天】 本文深入分析了Android和iOS两个主流移动操作系统在设计哲学、用户体验、技术架构等方面的显著差异。通过对比,揭示了这两种系统各自的独特优势与局限性,并探讨了它们如何塑造了我们的数字生活方式。无论你是开发者还是普通用户,理解这些差异都有助于更好地选择和使用你的移动设备。 ###
56 3
|
2月前
|
搜索推荐 vr&ar Android开发
Android vs. iOS:一场永无止境的辩论#### 一、
在当今的智能手机市场,Android和iOS无疑是两大巨头,它们各有千秋,吸引了全球数十亿用户。本文将深入探讨这两个操作系统的核心差异、优势以及未来发展趋势,帮助读者更全面地理解这场科技界的“双雄争霸”。 #### 二、
|
2月前
|
存储 前端开发 测试技术
Android kotlin MVVM 架构简单示例入门
Android kotlin MVVM 架构简单示例入门
40 1