在本系列的第一部分,咱們介紹了咱們在尋找可行架構的道路上所犯過的錯誤。在這部分,咱們將介紹傳說中的 Clean Architecture。android
當你在谷歌搜索 "clean architecture" 時,你看到的第一張圖片是:數據庫
它也被稱爲洋蔥架構,由於圖看起來象個洋蔥(你會意識到你須要寫樣板代碼寫到哭);或者是端口和適配器,由於你能夠看到右圖的一些端口。六角架構是另外一個類似的架構。網絡
Clean Architecture 是前面提到的 Uncle Bob 的心血結晶,他是 《代碼整潔之道》的做者。這種方法的要點是,業務邏輯(也稱爲 domain),是宇宙的核心。架構
當你打開項目時,你應該已經知道這個 app 是作什麼的,與技術無關。其它一切都是實現細節。譬如,持久化就是一個細節。定義接口,建立一個快速的粗糙的內存內(in-memory)實現,不要想太多,直到完成業務。而後你能夠決定怎樣真正地持久化數據。數據庫,網絡,二者結合,文件系統 —— 或者仍然保留在內存中,或者結果你根本不須要持久化。總之一句話:內層包含業務邏輯,外層包含實現細節。app
話說回來, Clean Architectue 有一些特性使這成爲可能:dom
依賴規則能夠用下圖解釋:佈局
外層應該依賴內層。那三個在紅色框框內的箭頭表示依賴。與其使用「依賴」,也許使用「看見」、「知道」、「瞭解」這類術語更好。在這些術語中,外層看見,知道,瞭解內層,但內層看不見,也不知道,更不瞭解外層。正如咱們先前所說,內存包含業務邏輯,外層包含實現細節。遵循依賴規則,業務邏輯既看不到,也不知道,更不瞭解實現細節。這正是咱們努力想要作到的。post
如何實現依賴規則取決於你。你能夠把它們放到不一樣的包,但當心「內層的」包不要使用「外層的」包。然而,若是有人不知道依賴規則,沒有什麼能夠阻止他破壞規則。一個更好的方法是把層分離到不一樣的 Android 模塊(modules,即子項目),並在構建文件(build.grale)中調整依賴,這樣內層就沒法依賴外層。ui
還有值得一提的是,雖然沒人能夠阻止你跨層依賴,譬如藍色的層的組件使用紅色的層的組件,但我強烈建議你只訪問相鄰的層的組件。3d
抽象原則以前已有所暗示。也就是說,當你朝圖中間移動時,東西變得更抽象。 這是有道理的:正如咱們所說內層包含業務邏輯,而外層包含實現細節。
甚至能夠在多個層之間劃分相同的邏輯組件,如圖所示。 內層定義更抽象的部分,外層定義更具體的部分。
舉個例子說清楚些。咱們能夠定義一個 「Notifications」 的抽象接口,並將其放到內層,這樣你的業務邏輯須要時可使用它來向用戶顯示通知。另外一方面,咱們能夠這樣來實現該接口,即便用 Android NotificationManager 顯示通知來實現,並把該實現放到外層。
以這種方式,業務邏輯可使用這樣的功能 —— 通知(在咱們的例子中)—— 但它不瞭解實現細節:實際的通知是如何實現的。此外,業務邏輯甚至不知道實現細節的存在。來看下面這張圖片:
當將抽象規則和依賴規則組合在一塊兒時,結果是使用通知的抽象業務邏輯既不會看到,也不會知道,更不會了解使用 Android NotificationManager 的具體實現。這很好,由於咱們能夠在業務邏輯絕不知情的狀況下切換具體實現。
讓咱們把這種規則組合和標準的三層架構簡單對比下,看看它們各自的抽象和依賴是怎樣的以及如何工做的。
在圖中,你能夠看到,標準三層架構的全部依賴最終都傳到數據庫。也就是說,抽象和依賴(方向)並不一致。在邏輯上,業務層應該是 app 的中心,但它卻不是,由於依賴朝向數據庫。
業務層不該該知道數據庫,應該反過來。在 Clean Architecture 中,依賴朝向業務層(內層),而且抽象也上升到業務層,所以它們很好地匹配。
這是重要的,由於抽象是理論,依賴是實踐。抽象是 app 的邏輯佈局,依賴關係是(組件)如何實際組合在一塊兒。在 Clean Architecture 中,這二者是匹配(譯者注:指方向一致)的。而在標準三層架構中則否則,若是你不當心,很容易致使各類邏輯上的不一致和混亂。
如今咱們將 app 分模塊,將全部內容分開,將業務邏輯放在咱們 app 的中心,並在外層實現細節,一切看起來都很棒。 可是你可能很快遇到一個有趣的問題。
若是你的 UI 是一個實現細節,網絡是一個實現細節,業務邏輯在中間,那麼咱們如何從互聯網獲取數據,通過業務邏輯,而後發送到界面?
業務邏輯在中間,應該協調網絡和界面,但它甚至不知道二者的存在。這是一個關於通訊和數據流的問題。
咱們但願數據可以從外層流向內層,反之亦然,但依賴規則不容許。 讓咱們舉個最簡單的例子。
咱們只有兩層,綠色和紅色的。綠色的是外層,它知道紅色的,紅色的是內層,它只知道本身。咱們但願數據從綠色流向紅色,而後折回綠色。該解決方案先前已經暗示過了,看下圖:
圖的右邊部分顯示了數據流。數據源於 Controller,通過 UseCase(或者替換成你選擇的組件)的輸入端口,而後經過 UseCase 自己,最後經過 UseCase 輸出端口發送到 Presenter。
圖的主要部分(左邊)的箭頭表示組合和繼承 —— 組合用實心箭頭表示,繼承用空心箭頭表示。組合也被稱做 has-a 關係,繼承被稱做 is-a 關係。圓圈中的 「I」 和 「O」 表示輸入和輸出端口。能夠看到,定義在綠色層中的 Controller,擁有一個(has-a)定義在紅色層中的輸入端口。UseCase(齒輪,業務邏輯,如今不重要)是一個(is-a)(或實現)輸入端口,而且擁有一個(has-a)輸出端口。最後,定義在綠色層中的 Presenter 其實是一個(is-a)定義在紅色層的輸出端口。
如今,咱們能夠將其與數據流匹配。Controller 擁有一個輸入端口 —— 擁有一個指向它的引用。它調用輸入端口的一個方法,這樣數據就從 Controller 流到輸入端口。但輸入端口是一個接口,而它的實際實現是 UseCase。也就是說,它調用 UseCase 的一個方法,這樣數據就流向了 UseCase。UseCase 執行某些操做,並但願將數據發送回來。它擁有輸出端口的一個引用 —— 輸出端口定義在同一層 —— 所以它能夠調用上面的方法。所以,數據流向輸出端口。最後 Presenter 是,或者實現了輸出端口,這是魔法的一部分。由於它實現了輸出端口,數據實際上流到它那了。
巧妙的是,UseCase 只知道它的輸出端口,世界在此中止(意指數據流到此結束)。Presenter 實現了它(輸出端口),實際上它能夠被任何對象實現,由於 UseCase 不知道或不關心這些,它只清楚其層內的一畝三分地。能夠看到,經過結合組合和繼承,咱們可使數據流向兩個方向,儘管內層並不知道它們在和外部世界通訊。瞄一眼下圖:
能夠看到,和依賴箭頭同樣,has-a 和 is-a 箭頭也指向中間。這是符合邏輯的。根據依賴規則,這是惟一可行的方法。外層能夠看到內層,但不能反過來。惟一複雜的部分是,is-a 關係儘管指向了中間,卻反轉了數據流。
請注意,定義輸入和輸出端口是內層本身的職責,所以外層可使用它們與其創建通訊。我說過,這個解決方案先前已經暗示過,並且已經有了。那個講解抽象的通知例子,也是這種通訊的一個例子。咱們在內層定義了一個通知接口,業務邏輯能夠用來向用戶顯示通知,可是咱們在外層也定義一個實現。在這種狀況下,通知接口是業務邏輯的輸出端口,用來和外部世界(在本例中,就是和具體的實現)通訊。你不須要把你的類命名爲 FooOutputPort 或者 BarInputPort,咱們命名端口只是爲了解釋理論。
那麼,它是過分複雜,過分費解的過分工程嗎?好吧,當你習慣了,它就簡單。而且這是必要的。它容許咱們使得好的抽象/依賴實際匹配真實世界的通訊和工做。也許這一切都提醒你不過是空中樓閣:美麗,理論上優雅,但過於複雜,咱們仍然不知它是否有效,但在咱們的案例中,它確實有效。
這就是本系列的第二部分。最後,第三部分,畢竟咱們已經瞭解了理論和架構,將講解全部你須要瞭解的那些圖上的標籤。換句話說,分離的組件。咱們將向你展現一個真實的應用於 Android 的 Clean Architecture。