写出高质量软件是困难和复杂的:不只仅是为了知足需求,还应该是健壮的,可维护的,可测试的,而且足够灵活以适应成长和变化。这就是洋葱架构出现的缘由,它表明一组优秀的开发实践,用来开发任何的软件应用都是一个不错的方式。html
洋葱架构,也成为整洁架构(The Clean Architecture),用来构建具备以下特色的系统:android
看到这张图,你应该能理解为何称其为洋葱架构了:D, 没错,这就是它的原理图。注意,并非只能使用4个圆环,重点在于这里的依赖原则:代码依赖是从外向内的,内环中的代码不该该知道外环中的任何东西。git
这里有一些相关的词汇能够帮助更好的理解和熟悉这种方式:github
Entities:应用的业务对象。数据库
Use Casess: Use Casess协调(Orchestrate)数据从Entities的流入和流出,也被称为Interactors。安全
Interface Adapters:这个Adapter集为Use Casess和Entities把数据转换为方便使用的格式(如渲染展现在页面上),Presenters和Controllers属于这里。数据结构
Frameworks and Drivers:这是实现全部细节的地方:UI,Tools,Frameworks等。架构
下面用一张更生动图来辅助说明它的原理:框架
上面的同心圆表明软件的不一样部分。总得来讲,越往里面,代码级别越高。外层的圆是(实现)机制,而内层的圆是原则(Policies)。学习
让这个架构起做用的最主要原则是依赖原则。这个原则要求源码依赖只能指向内部。内部的圆不能知道外圆的任何事情。通常来讲,外圆的声明(包括方法、类、变量或任何软件实体)不能被内圆引用。
一样的,外圆使用的数据格式不能被内圆使用,尤为是外圆中的Framework产生的格式。咱们不想让外圆的任何东西影响内圆。
越往里面抽象级别越高,最外层的圆是低级别的具体细节。越往里面内容越抽象,而且封装更高级别的原则(Policies)。最里面的圆是最通用的。
Entities封装了企业级的业务规则。一个Entity能够是一个带方法的对象,也能够是一个数据结构和方法集。Entities能够被用于企业的其余应用。
若是你没有加入企业,而是仅仅在写一个简单的应用,那么这些Entities就是这个应用的业务对象。它们封装了最通用、最上层的原则。它们是最不容易改变的,即便外部的东西改变了。例如,你不想让这些对象受到页面导航、安全的影响。应用的任何操做变化都不该该影响Entities Layer。
这一层包含了应用特有的业务规则。它封装和实现了系统的全部用例。这些用例协调数据从entities的流入和流出,而且指导entities使用它们的企业级业务规则来达到用例的目标。
咱们不但愿这一层的改变影响到Entities,同时也不但愿这一层被外层的改变影响,如外层的数据库,UI或者任何Frameworks的改变,这一层独立于这些关注点。
固然,咱们确实指望应用的操做变化影响用例层。若是一个用例的细节改变,那么这一层的部分代码确实会受到影响。
这一层包含一个adapters set(数据适配器集),它们把适用于Use Casess和entities的数据转换为适用于外部服务(external agency,如Database或Web)的格式。 例如,这一层能够彻底包括GUI的MVX架构,Presenters, Views和Controllers都属于这里。Models可能仅仅是从Controllers传到Use Casess的数据结构,而后从Use Casess返回给Presenters和Views。
这一层的数据会被转换,从适用于entities和Use Casess的格式转换到适用于所使用的持久化框架的格式(如数据库)。这个圆之内的代码不该该知道关于数据库的任何东西。若是是一个SQL数据库,那么全部的SQL应该被限制到这一层,而且一般来讲是被限制到层中跟数据库有关的部分。
一样,这一层也须要一些其余必要的Adapter来把外部的数据格式(如来自于外部服务的格式),转换为适用于Use Casess和entities的格式。
最外面的一层一般由Frameworks和Tools组成,如Database,Web Framework等。通常来讲,除了用于和内层圆交互的链接代码,你不会在这一层写不少代码。
这一层是实现全部细节的地方。Web和Database都是须要实现的细节。咱们把这些东西放在外面以减轻来自于它们的伤害(即减轻对他们的依赖)。
在图的右下角是一个咱们应该如何跨界的例子。它展现了Controllers、Presenters与下一层的Use Casess的交互。注意控制的流向,它开始于Controller,通过Use Casess,最终在Presenter中执行。同时也请注意Source Code依赖,它们每个都指向内部的Use Casess。
咱们一般用依赖倒置原则来解决这个明显的矛盾。好比,在Java这样的语言里,咱们可使用接口和继承关系在合适的地方让源码依赖与控制流反向来跨界。
例如,假设Use Cases须要访问Presenter,固然,不能是直接访问,否则会违反依赖原则,因此咱们让内圆的Use Cases访问一个接口(如图中的Use Cases output port),而后外圆的Presenter实现这个接口。
在这个架构中,一样的技术也被用于跨越其余的边界。咱们利用运行时多态来建立与控制流相反的SourceCode依赖以知足依赖原则,不管控制流是如何流向的。
一般跨界的数据都是简单的数据结构。你可使用简单的结构或数据传输对象(Data Transfer Object)。这个数据能够简单的是方法调用的参数,你也能够把它包装到一个HashMap或者一个对象。最重要的是独立的、简单的数据结构才能跨越边界。不要投机取巧,如传输Entites或者Database rows。咱们不想让这个数据结构有任何违反依赖原则的依赖。
例如,不少的数据库框架对于query返回一个方便的数据格式,咱们能够称之为Row Structure,咱们不想向内部传递这个row structure。这会让内圆知道外圆的内容而违反了依赖原则。
因此,咱们应该以最适用于内圆使用的格式来传递跨界的数据。
知足这些简单的原则并不难,而且会减小项目进程中不少头疼的问题。经过把软件分红几层,而且知足依赖原则,你将会建立一个自己就可测试的系统,同时还有其余的好处。当系统的任何外层部分(如Database,Web 框架)废弃的时候,你能够轻松的替换这些废弃的元素。
代码实践能够参考:https://github.com/android10/Android-CleanArchitecture/releases
本文大部份内容译自The-Clean-Architecture,其中加入了本身学习该架构时的理解,若有意见和建议,欢迎交流!
The-Clean-Architecture:http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html
Architecting-Android-The-Clean-Way:http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/
Architecting Android…The evolution:http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/