Android MVPR 架构模式

最近我在尝试让 Google 的 IO App 变得可单元测试,我这样做的其中一个原因是验证 Freeman 和 Pryce 在引用中对单元测试的总结。即使现在我还是没有把 IOSched 中的任何一个 Activity 重构,但我已经在重构代码的过程中感受到他们所说的东西了。

我现在在重构的 Activity 是 SessionDetailActivity,如果你一直有在关注我的话就会知道我说的是哪个 Activity,但如果你只是第一次看我的博文,你可以看看下面这张图了解下 SessionDetailActivity 的界面是咋样的。

就像我在这个系列博文的序中所说,要让 SessionDetailActivity 可被单元测试,有几个麻烦必须解决。我在这个系列的上一篇博文中说过,对它动态构建的 View 进行单元测试是一个挑战,但在那篇博文中,我提到我解决这个问题的办法并不能治本,因为在 View 和 Presenter 之间存在着循环依赖。

循环依赖是 Android 应用架构存在大问题的征兆:Activity 和 Presenter 都违反了单一职责原则,它们至少需要完成两件事:为 View 绑定数据并对用户的输入作出相应。这也是为什么 SessionDetailActivity 这个类会作为 Android 开发的 Model 被使用,使得类的代码数超过1000行。

我坚信有更好的办法架构我们的应用,在接下来的博文里,我会提出一种拥有以下特性的新架构:

  1. 将通常由 Presenter 和 Activity 负责的多重职责打破
  2. 打破一般存在于 View 间或 Activity 和 Presenter 之间的循环依赖
  3. 允许我们用构造方法对所有为用户展示数据以及相应用户输入的对象进行依赖注入
  4. 让 UI 相关的业务逻辑易于进行单元测试,而且不可能在没有必要的依赖时被构建以履行他们的职责,而且通过利用聚合和多态性修改对象的行为。

在这片博文中,我会尝试总结开发新的 Android 应用架构的原因。

为什么需要新的架构?

Activity/Fragment/Presenter 会变得臃肿

Activity 和 Fragment(接下来我会统称为 Activities,但我说的也适用于 Fragment)是违反单一职责原则的典型:

  • 处理 View 的事件
  • 更新数据 Model
  • 调用其他 View
  • 与系统组件交互
  • 处理系统事件
  • 基于系统事件更新 View

正如 Richa 所说,这些职责大部分从 Activities 中剥离,但即使我们这样做了,Activities 还是违反了单一职责原则。即使是最简单的 Activities 还是需要将 Model 的数据和 View 绑定,并对用户输入作出相应,例如:.

[代码]java代码:

public class SessionDetailActivity extends BaseActivity implements
        LoaderManager.LoaderCallbacks,
        ObservableScrollView.Callbacks {

    //...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //Responsibility 1: Responding to user‘s action (in this case, a click)
        mAddScheduleButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                boolean starred = !mStarred;
                SessionsHelper helper = new SessionsHelper(SessionDetailActivity.this);
                showStarred(starred, true);
                helper.setSessionStarred(mSessionUri, starred, mTitleString);
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    mAddScheduleButton.announceForAccessibility(starred ?
                            getString(R.string.session_details_a11y_session_added) :
                            getString(R.string.session_details_a11y_session_removed));
                }

                /* [ANALYTICS:EVENT]
                 * TRIGGER:   Add or remove a session from My Schedule.
                 * CATEGORY:  ‘Session‘
                 * ACTION:    ‘Starred‘ or ‘Unstarred‘
                 * LABEL:     Session title/subtitle.
                 * [/ANALYTICS]
                 */
                AnalyticsManager.sendEvent(
                        "Session", starred ? "Starred" : "Unstarred", mTitleString, 0L);
            }
        });

        //...

        //Responsibility 2: Fetching and binding data to the view
        LoaderManager manager = getLoaderManager();
        manager.initLoader(SessionsQuery._TOKEN, null, this);
        manager.initLoader(SpeakersQuery._TOKEN, null, this);
        manager.initLoader(TAG_METADATA_TOKEN, null, this);
    }

Google IOSched 应用中的 SessionDetailActivity 就是 Activity 即使只负责绑定数据到 View 中和响应用户输入也会变得臃肿的绝佳范例。即使我们把这部分代码从 SessionDetailActivity 中剥离,还是有一个类有700多行代码。不信我?你大可以去看看源码,Presenter 也会因为 Activity 那样的原因变得臃肿:Presenter 通常负责绑定数据以及响应用户输入,所以 Presenter 也需要像 Activity 那样通过剥离额外的职责被瘦身。

Activities/Fragment/Presenter 通常在 View 间存在循环依赖

Activities 通常通过它们和 View 之间的循环依赖履行绑定数据到 View 和响应用户输入的职责(例如:作为 setContentView() 方法参数的 View)。下面是范例:

[代码]java代码:

mAddScheduleButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                boolean starred = !mStarred;
                SessionsHelper helper = new SessionsHelper(SessionDetailActivity.this);
                showStarred(starred, true);
                helper.setSessionStarred(mSessionUri, starred, mTitleString);
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    mAddScheduleButton.announceForAccessibility(starred ?
                            getString(R.string.session_details_a11y_session_added) :
                            getString(R.string.session_details_a11y_session_removed));
                }

                /* [ANALYTICS:EVENT]
                 * TRIGGER:   Add or remove a session from My Schedule.
                 * CATEGORY:  ‘Session‘
                 * ACTION:    ‘Starred‘ or ‘Unstarred‘
                 * LABEL:     Session title/subtitle.
                 * [/ANALYTICS]
                 */
                AnalyticsManager.sendEvent(
                        "Session", starred ? "Starred" : "Unstarred", mTitleString, 0L);
            }
        });

SessionDetailActivity 持有对 mAddScheduleButton 的引用,而且 mAddScheduleButton 也持有对 SessionDetailActivity 的引用。我等会会说,这样的循环依赖限制我们通常用于 Activities 中实现 UI 相关的业务逻辑的方法。

MVP 的 Presenter 有着和它们和 View 相同的循环依赖,在我能详细解释之前,我必须简单地介绍传统 Android 应用架构中 View 和 MVP 模式中 View 的区别。

MVP 模式中的 View 就像我定义的,只是 MVP 模式三巨头其中之一,通常被定义为一个接口,而且一般会在 Activity,Fragment 或 Android 传统架构中的 View 中实现。Android 传统架构中的 View 就像它的名字,是一个 View 的子类。

使用 MVP 模式中的 View 和 Presenter 仅仅是在它们之间无形中重新创建了和 Android 传统架构中 View 和 Activities 之间相同的循环依赖。

Presenter 需要 MVP 模式中的 View 使得它们能绑定数据到 MVP 模式中的 View,MVP 模式 中的 View 需要对 Presenter 的引用,使得它能传递点击和其他 UI 相关的事件给 Presenter。Square 的博文就有存在着循环依赖的 MVP 模式的实现。

循环依赖在你想要为单元测试构建对象(或通常情况下)都会产生问题。然而,通常情况下,我们都不会把 MVP 模式的 View 和 Presenter 或 Activities 和 View 间的循环依赖当作问题,因为 Activities 和 Fragment 被系统初始化,而且因为我们并没有用依赖注入去注入 Activity 和/或 Fragment 的依赖。相反的是,我们只是初始化了 Activity 在 onCreate() 方法中需要的任何依赖:

[代码]java代码:

public class MyActivity extends Activity implements MVPView {

    View mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_browse_sessions);
        //...
        final Presenter presenter = new Presenter(this);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                presenter.onButtonClicked();
            }
        });
    }
}

初始化在 onCreate() 方法中依赖的混合类,然而,限制我们使用组合和多态性去实现 UI 相关的业务逻辑。下面是一个你应该使用多态性实现 UI 相关的业务逻辑的例子:假设你开发了一个被用户使用的应用,而且用户在不同的等级时有不同的特权,那么他们需要通过邮件验证或回答其他用户提的问题以提高等级。我们可以想象有许多按钮用于完成依赖等级完成的不同功能,或 View 由用户等级决定的初始状态。多态性为我们提供整洁,可拓展的方式去实现这样的逻辑:我们创建一个 Presenter 用于为用户绑定不同的等级,不管用户在什么等级中,我们都能把 MVP 模式中的 View 传到特定的 Presenter 子类中,并让该子类处理相应的点击事件或者基于用户的等级呈现 UI。当然了,还有许多架构 Android 应用的方式,使得我们能够在存在 Presenter 和 MVP 模式中的 View 间循环依赖的情况下利用多态性,但这些方法都不够优雅,或者说他们为了完成单元测试作出了极大的贡献。

这篇博文剩下的篇幅已经不足以让我一一细述我记得的那些解决方法,但我能简要的说说为什么解决 MVP 模式中的 View 和 Presenter 间循环依赖的方法不理想。你可以想象我们可以只创建一个 MVP 模式的 View 或 Presenter,而没有它们履行职责所需的任何依赖。换句话说,我们可以像下面这样:

[代码]java代码:

public class MyActivity extends Activity implements MVPView {

    View mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_browse_sessions);
        //...
        final Presenter presenter = new Presenter();
        //****
        presenter.setView(this);
        //****
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                presenter.onButtonClicked();
            }
        });
    }
}

这样我们就能通过多态性解决上面提到的问题,但这并没有打破循环依赖。它能做的是允许我们在无效状态创建一个对象。这并不是最简洁的解决办法,把这放在 Freeman 和 Pryce 话里:

“创建或不创建,不需要尝试”

我们想要确保总是创建有效的对象,部分地创建对象然后通过设置它的属性完成它是脆弱的……

结论

Presenter 和 Activities 违反了单一职责原则,他们常常负责绑定数据到 View 中和响应用户的输入,这些都会使 Activities 和 Presenter 变得臃肿。

Presenter 和 Activities 常常会因为他们和 View 间的循环依赖拥有多重职责,即使这样的循环引用不会带来什么问题,但这会更难以对 View 和/或 Presenter 进行单元测试,而且会限制我们使用多态性实现 UI 相关的业务逻辑。

就像我之前说的,我认为会有一种架构应用的办法不会有上面这些烈士,在下一篇博文中,我会提出可供选择的架构。

时间: 2024-10-09 23:49:27

Android MVPR 架构模式的相关文章

Android MVPR 架构模式-Part1

Android MVPR 架构模式-Part1 原文链接 : MVPR: A FLEXIBLE, TESTABLE ARCHITECTURE FOR ANDROID (PT. 1) 原文作者 :Matthew Dupree 译文出自 : 开发技术前线 www.devtf.cn 译者 : chaossss 校对者: Mr.Simple 状态 : 完成 全面的单元测试能提高内部系统的代码质量,因为系统的每一个组件都需要被测试,因此每个单元都需要在系统外被构建,在测试环境中进行测试.对对象进行单元测试

Android 四大组件 与 MVC 架构模式

作为一个刚从JAVA转过来的Android程序员总会思考android MVC是什么样的? 首先,我们必须得说Android追寻着MVC架构,那就得先说一下MVC是个啥东西! 总体而来说MVC不能说是一个设计模式,因为划分维度太大,所以MVC应该属于架构模式! 百度百科 —— MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑和数据显示分离的方法组织代码,将业务逻辑被聚集到一个部件

Android APP架构心得

前言 从JavaEE转到Android开发也2年多了,开发的项目也有4,5个了(公司项目),其中有3个项目前期都是自己独立开发,从一开始的毫无架构到现在对如何架构也有一点心得,所以在此分享出来,大家一起交流 什么是架构 在我看来,软件架构绝对不只是框架的堆砌,看我看来,架构是为了方便软件维护.扩展.安全性.切入性(我也不知道有没有人提出过这个关键字,因为的确很少看见,简单来说我这里说的切入性就是指一个以前没有接触过这个项目的人,能快速加入到这个项目中,对项目进行维护.修改和扩展) 维护性 一个好

Android应用架构

Android开发生态圈的节奏非常之快.每周都会有新的工具诞生,类库的更新,博客的发表以及技术探讨.如果你外出度假一个月,当你回来的时候可能已经发布了新版本的Support Library或者Play Services 我与Ribot Team一起做Android应用已经超过三年了.这段时间,我们所构建的Android应用架构和技术也在不断地演变.本文将向您阐述我们的经验,错误以及架构变化背后的原因. 曾经的架构 追溯到2012年我们的代码库使用的是基本结构,那个时候我们没有使用任何第三方网络类

mvp架构模式

今天是国庆节,祝大家节日快乐,愿祖国越发繁荣昌盛.假期程序员也不能偷懒,更新一些博文吧. 看到封面图片喜欢NBA的人可能很容易就想到了最有价值球员.但是此mvp非彼MVP,此mvp指的是现在Android开发中比较常见的一种软件架构模式.mvp架构模式是Google官方推荐的架构模式,特别是近年来的新项目,mvp+retrofit+rxjava+dragger2配合使用已经在引领程序界的潮流了,在github上可以轻易的搜到一大堆这样的开源项目.前端时间笔者也在公司的一个sdk上进行了尝试,在此

【译】Android应用架构

Android开发生态圈的节奏非常之快.每周都会有新的工具诞生,类库的更新,博客的发表以及技术探讨.如果你外出度假一个月,当你回来的时候可能已经发布了新版本的Support Library或者Play Services 我与Ribot Team一起做Android应用已经超过三年了.这段时间,我们所构建的Android应用架构和技术也在不断地演变.本文将向您阐述我们的经验,错误以及架构变化背后的原因. 曾经的架构 追溯到2012年我们的代码库使用的是基本结构,那个时候我们没有使用任何第三方网络类

Qualcomm Android display架构分析

Android display架构分析(一) http://blog.csdn.net/BonderWu/archive/2010/08/12/5805961.aspx http://hi.baidu.com/leowenj/blog/item/429c2dd6ac1480c851da4b95.html 高通7系列硬件架构分析 如上图,高通7系列 Display的硬件部分主要由下面几个部分组成: A.MDP 高通MSM7200A内部模块,主要负责显示数据的转换和部分图像处理功能理,如YUV转RG

android MVVM开发模式(一)

android MVVM开发模式 概念 mvvm 是一个在 mvp 架构上修改,目标是将view的一些更改,跟model关联起来,使得model的数据改变,直接通知到view上面来,从而解决mvp架构里面的v-p之间的接口太重问题. 所以mvvm的核心解决问题为:使得v-p直接的关系弱化,使用绑定方式(dataBinding)直接将model的改变反馈到view上面. 关于完整的dataBinding讲解,请看这里 https://github.com/LyndonChin/MasteringA

Android系统架构之微服务架构

目录 一.微服务架构模式 1.1 模式描述 1.2 模式拓扑 1.3 避免依赖与调度 1.4 注意事项 1.5 模式分析 二.Android中的微服务架构 三.结语 前段时间我们翻译的<软件架构模式>( 完整书籍的地址 ) 对外发布之后得到了大家的一致好评,书中讲述了五种经典.流行的软件架构模式,同时分析了五种模式的实现.优缺点等,为我们的开发工作提供了很有价值的指导.但是<软件架构模式>的问题在于没有结合具体的示例来让这些理论知识更易于吸收,因此有些同学在我的开发群反馈: 书看起