寫出高質量軟件是困難和複雜的:不只僅是爲了知足需求,還應該是健壯的,可維護的,可測試的,而且足夠靈活以適應成長和變化。這就是洋蔥架構出現的緣由,它表明一組優秀的開發實踐,用來開發任何的軟件應用都是一個不錯的方式。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/