浅谈设计

 

概述

设计指导着代码的实现,帮助阅读者理清逻辑的脉络,提高代码的可读性。另一方面优秀的设计者,可以敏锐的发现程序中可能存在的变更点,提前作出准备,来应对后面可能出现的变更。设计者们有着自己的武器箱,包括但不限于:“抽象”、“隔离”、“封装”、“设计模式”、“设计思想” 。

 

 

业务设计

业务层的设计,是最贴近于具体实现的,它属于低级策略层(越贴近具体IO,层级越低),这一层也是最不稳定的,我们更多考虑的是它的灵活性。无疑、抽象是提高灵活性的有效手段,但是过多的抽象会让本该连贯的逻辑变的断断续续。如果你确实这么做的,可能会有人站出来说你过度设计了。所以,除非你有了完整的设计文档,来指导其他人来阅读你的代码,一般情况下在这一级别,我们要省着点用你的“抽象”武器。那该怎么做?

我的建议是更关注于代码的分层和对象的设计,将业务先分解成几个高级策略类,再由策略类来决定具体的细节实现,对,就是层层分解,由大化小的思想。你不知道怎么做?好在,有几个比较成熟的设计方案可用。

 

 

MVC模式

设计界的老人,存在历史悠久,这里就不详细说明了,这方面的资料很多。对于MVC而言,最大的特点在于,将业务分成三层(视图、控制、模型),而没有限制层之间的调用关系,后面说到的MVP、MVVM也都是分为这三层。我一般将MVC理解为三层设计模型的统称,所以基于该模型设计出的MVX都属于它的子集。

早期android开发也都是基于这种模型,来设计业务代码的,有些人说,这种设计会带来一些弊端。比如说会导致Activity臃肿、代码难以测试、可读性差、维护成本高。但我认为,MVC只是一种思想模式,本身并不会带来什么副作用,而造成影响的,其实是我们的具体实现。最多的一种观点是MVC下Activity会承担View和Control的职责,导致臃肿,可MVC并没有让你这么定义Activity,我们可以将Control的职责抽离出来,封装了另外一个类(MVP),也可以将View层的职责抽离出来。是的,google为了让你把它抽离出来,发明了MVP、MVVM,本来它们应该没有名字,一切都是为了让大家重视起来,所以它有了名字。以下是我搜索的一些设计图,可以看出层与层之间的交互方式很随机:

 

浅谈设计浅谈设计

 

浅谈设计

 

 

MVP模式

 

我记得好像是2016年(懒得搜,不确定),Google推出MVP+单元测试。有了名字就是好,大家开始重视起来了。但是大多数人,喜欢把MVP和单元测试分开来看,甚至是忽略单元测试。实际上google是为了推单元测试,然后通过MVP来实现单元测试,对的,MVP为了实现单元测试,如果你没有单元测试的需求,单独用MVP好在哪里呢?之前Activity不臃肿了,Presenter开始臃肿了。

MVP主要是为了对View层进行抽象,这样做的目的是因为,View最难以测试,它依赖于Android环境,无法达到高效稳定的测试。所以Presenter与View交互都是通过View的接口,如果你没有单元测试的需求,这样做纯粹脱裤子放屁,我看到有人说为了Presenter的可重用(考虑下真的有重用需求吗?)

说到单元测试,一般用Junit+Mockito+ Robolectric。可以实现在Java环境下执行单元测试。感兴趣的话,我单独出一期。

以下是网上搜索的设计图,可以看到不仅设计了分层,也限制了层与层之间的交互方式:

https://github.com/long8313002/AndroidMVP(示范代码)

 

浅谈设计

 

 

MVVM模式

MVP的升级款,考虑到在MVP下,Presenter通过接口来获取View层的数据来作为参数,然后通过Model来进行数据处理,处理结果通知到Presenter,然后在设置View。这样,View将手动定义很多接口方法,很不方便。MVVM通过双向绑定,将View上的数据映射到了一个Bean中,对Bean的操作等于操作的View。Presenter与View的交互再也不用通过接口了,直接持有Bean的引用来进行操作。

https://github.com/long8313002/AndroidMVVM(示例代码)

 

浅谈设计

 

 

 

模块设计

 

业务层颗粒度仅仅到Activity、Fragment单个页面级别。当项目简单的时候,以这个颗粒度进行划分已经足够了,但是当项目足够复杂,我们就需要考虑划分更大的颗粒度出来了。

一般情况下,我们将业务类似的页面放在同一个模块,我们称这样的模块叫做“业务模块”(专门负责业务),对于工具类,通用服务、核心库我们一般放在“核心模块”,而对于协调

模块间交互,模块接口定义等高级策略,我们一般放在“通用模块”。这里需要了解下“依赖倒置”,一般情况下“通用模块”属于高级策略,相对比较稳定(负责的是模块维度的交互,因为不牵扯到业务细节,所以稳定),由于它负责模块间的交互,按道理应该依赖“业务模块”,来控制它们(高级策略一般为控制层、负责作出决策)。但是这样会导致稳定层依赖不稳定层,降低整个架构的稳定性,所以我们需要依赖倒置,让“业务模块”依赖“核心模块”,并将具体实现,注入到“通用模块”来,使它通过控制接口的方式,来实现逻辑分发。

 

 

依赖反转原则(DIP)

上层模块不依赖底层模块依赖抽象,抽象不依赖细节,细节依赖抽象,针对接口编程。

在面向过程的开发中,依赖关系总是自上而下,上层模块依赖和调用下层模块,当下层模块剧烈变更时,上层模块总是会跟着改动,变化会从下向上传递。

在面向对象的开发中为了解决这个问题,遵循依赖倒置原则,构建稳定的抽象层,当细节变更了,也不容易引起抽象层的变更,这样客户端更稳定。

 

 

 

架构设计

 

这里谈的架构,实际上“顶层架构”,在这里我们定义最*的策略类,它们是高度抽象(为了扩展),且不易改变的(改动成本非常高)。这一层可能实际代码不是很多,但是做的事情其实不少,大到项目结构选型,技术方向;小到代码命名等代码规范问题。有很多思想无法直接落到代码上,往往我们会出设计文档,架构图,来帮助开发者们可以更好的协调工作。

 

当你开始接手这些工作的时候,你往往正在向架构师靠近,虽然大多数情况下公司并没有给你这个头衔,但是你也应该将你的视野放在更高的层次上来了。你设计好了软件的蓝图,虽然你把它展现给其他开发者看了,你仍然需要关注它们的代码,并在必要的时候进行纠正并给出指导,这似乎是架构师全部的工作,给出设计蓝图,并监督和帮助大家把它完成。

 

如果你是一个业务的开发者,你只需要关注业务是否可以按时上线,并且保证上线前有足够高的质量。如果你是一个模块负责人,你需要关注模块的性能、质量、crash率、代码规范,并能在自己模块内针对业务做出设计。如果你是架构师,你更多的关注提高开发效率、制定模块间交互规范,你可以指导他们制定业务架构,但这不是必须的。另外你需要关注项目中遇到的问题,并确定技术选型,你可以把问题抛出来让大家讨论,但最后你需要做决定。

 

这一层的设计比较抽象、一般很难在代码上有所体现,但其实可以写一些脚本来帮助你完成上面提到的职责。如果你审查代码,来保证实现上符合规范,这个低效且辛苦的,我们可以使用自定义lint的方式,来自动进行代码检测。另外可以通过模块化或者插件化来做代码隔离、推行单元测试来降低crash率,使用框架(三方或者自己开发)来提高开发效率等等。