如何用整洁的方式架构Android应用

原文:http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/


在过去的几个月,在我和同事@pedro_g_s和 @flipper83(顺便说下,这两个同事是Android开发厉害角色)在 Tuenti 友好的讨论之后,我认为现在是写一篇关于架构Android应用的好时机。

这篇文章的目的是秀一下过去几个月萦绕在头脑中一些方法,加上我从调查和实现中学到的一些事情。

入门

我们都知道,写高质量软件是困难和复杂的:软件不只是满足需求,而且应该足够健壮、可维护、可测试和灵活来适应发展和变化。这个就是“ 整洁架构”的来源,而且是一个可以应用的好方法当开发任何应用软件。

思想很简单: 整洁架构代表着一组可以用来构建系统的实践:

  • 独立于框架层
  • 可测试
  • 独立于UI
  • 独立于数据库
  • 独立于任何外部代理

从上面图片可以看到四个环,当然不必需只是四个环,图片仅仅概要的。但是你应该考虑到 依赖规则(Denpendency Rule)
源代码依赖仅仅指向内部,但是内环代码不能知道外环的任何事情

下面一些能够更好熟悉和理解这个方法的一些术语:

实体(Entities):  应用的业务对象
用例(Use Case):  这些用例协调流向或者流出实体的数据流,也叫做交互件
接口适配器(Interface Adapters): 这些适配器集合用来转换数据,这些数据格式是对用例和实体方便使用的
框架和驱动(Frameworks and Drivers): 这个就是细节所在: UI、工具、框架等等。

想要更好和广泛的介绍,请参考 这个文章这个视频

我们的情景

为了事情进行,我从一个简单的情形开始:仅创建一个小应用,来显示从云端获取的好友和用户列表,当点击其中的一个,将会打开一个新的屏幕来显示这个用户的更多的信息。
这里给你一个视频,以便你可以了解我正在说什么的一个蓝图

(注: 视频不能播放)

Android 架构
目标是,通过业务规则完全不知道外部世界,来实现 关注分离(separation of concerns)。所以,业务规则可以在没有依赖外部元素的情况下测试。
为了达到这一点, 我的建议是,把工程分解为三个不同的层级,在每个层级有自己的目的,而且和其他层级相互隔离。
值得一提的是,每个层级有自己的数据模型,所以这个独立性是可以达到的(你将会在代码中看到,需要数据映射来完成数据迁移。当你不想你的模型在整个应用中交叉使用,你需要付出这个代价)
下面是一个图解,用来说明这个是看上去是什么样的:
clean_architecture_android
注意:我没有使用任何外部库(除了gson库来解析json数据,junit,mockito, robolectric和espresso来测试)。理由是这使得这个例子变得更加清楚。不管怎样, 请不要犹豫添加ORMs来储存磁盘数据,或者任何依赖注射框架,或者任何你熟悉的工具和库,这些都会使得生活更加便利。(记住, 重新发明轮子不是一个好的实践)。

呈现层

有关视图和动画的逻辑发生在这里。这里仅仅是使用了 模型-视图-呈现(Model View Presenter)(从现在开始缩写 MVP ),但是你可以用其他的模式,像MVC或者MVVM。我不想涉及到关于它的细节。但是 fragments和activities在这个只不过是视图,除了UI逻辑,没有其他的逻辑在他们里面,而且所有的渲染事务都是在这里发生的。这个层级的 呈现是和 交互件(用例)合成的,这些交互件是在Android UI线程以外的新的线程来完成工作的,然后通过一个回调返回将要在视图中渲染的数据。
clean_architecture_mvp
 如果你想要一些比较酷的关于使用MVP和MVVM的 Effective Android UI 例子,可以看看我的朋友Pedro Gómez的做的事情

领域层

业务规则放在这里:所有逻辑发生在这里。关于Android项目,你也将看到所有的交互件(用例)的实现在这里。 这个层级是一个没有任何Android依赖的纯粹的Java模块。当连接业务对象时所有的外部组成部分应当使用接口。
clean_architecture_domain

数据层

应用所需的所有数据,通过一个UserRepository的实现(接口在领域层),都来自这个层级。这个实现使用仓储模式,即一个通过一个工厂根据不同的条件选择不同的数据源的策略。
比如,当通过id来获取一个用户,如果这个用户已经在磁盘缓冲,将选择磁盘缓冲数据源,否者,将查询云端来获取数据
,然后保存数据到磁盘缓冲。
所有这些背后的思想是数据来源对客户端来说是透明的,即客户端是不关心数据是否来自内存,磁盘,还是云端。唯一需要的是,数据能够来到和得到。

clean_architecture_data
注意: 在代码中,我利用文件系统和Android preferences实现了一个简单初级的磁盘缓冲,这只是为了学习的目的。再次记住,如果有库更好地干这些工作,你就 不应该重新发明轮子。

错误处理

这个是一个永远需要讨论的主题。如果你能够分享你的解决方案,我将感激不尽。
我的策略是使用回调。比如,如果数据仓储发生了社么,将有两个回调方法  onResponse() 和  onError(). 后一个在“ErrorBundle”包装类里封装了异常。这个方法带来一些麻烦,因为回调链条是一个接一个直至这个错误走到渲染的呈现层的。代码可读性会打一些折扣。
另外一方面,我可以实现一个事件总线系统,如果某些事情出错了,系统就抛出事件。但是这个解决方案有点像  GOTO,而且依我看来,当你订阅了一些事件时如果你没有仔细控制,有时你会变得很迷惑。

测试

关于测试,我倾向于不同的层级不同的解决方案:
  • 呈现层:用Android instrumentation 和 espresso来集成和功能测试
  • 领域层: 单元测试这里用Junit 加上mockito
  • 数据层: 集成和功能测试用Robolectric(这个层级有Android依赖)、junit和mockito

秀上代码

我知道你一直在想代码在哪里呢。呵呵,这个是代码的 github链接,在这里你可以发现我做了什么。关于目录结构,有一些需要声明,不同层级使用模块表示
  • 呈现:这个是呈现层的Android模块
  • 领域:一个没有Android依赖的模块
  • 数据 获取所有数据的Android模块
  • 数据测试: 数据层的测试。使用robolectric时,由于一些限制,我不得不在独立的一个Java模块中使用

总结

就像鲍勃叔叔说的, 架构是目的,而不是框架。我完全同意这个箴言。当然,做不同的事情(不同的实现)有不同的方式,我非常肯定你就像我一样每天面对着不同的挑战,但是,通过这个技巧,你可以肯定你的软件能
  • 容易维护
  • 容易测试
  • 非常具有结合性
  • 解耦合

作为总结, 我强烈推荐你着手试试看,分享你的结果和经验,发现有没有其他更好的方法,我一直坚信, 持续改进永远是正确的事情。
希望这篇文章对你有所帮助,和以前一样,非常欢迎反馈

链接和资源

// 花了四个小时翻译校对,欢迎指正,请勿拍砖。



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值