[譯] Android 架構:Part 2 —— 介紹 Clean Architecture

在本系列的第一部分,我們介紹了我們在尋找可行架構的道路上所犯過的錯誤。在這部分,我們將介紹傳說中的 Clean Architecture。

當你在谷歌搜索 "clean architecture" 時,你看到的第一張圖片是:

它也被稱為洋蔥架構,因為圖看起來象個洋蔥(你會意識到你需要寫樣板代碼寫到哭);或者是端口和適配器,因為你可以看到右圖的一些端口。六角架構是另一個相似的架構。

Clean Architecture 是前面提到的 Uncle Bob 的心血結晶,他是 《代碼整潔之道》的作者。這種方法的要點是,業務邏輯(也稱為 domain),是宇宙的核心。

掌控你的領域(domain)

當你打開項目時,你應該已經知道這個 app 是做什么的,與技術無關。其它一切都是實現細節。譬如,持久化就是一個細節。定義接口,創建一個快速的粗糙的內存內(in-memory)實現,不要想太多,直到完成業務。然后你可以決定怎樣真正地持久化數據。數據庫,網絡,兩者結合,文件系統 —— 或者仍然保留在內存中,或者結果你根本不需要持久化。總之一句話:內層包含業務邏輯,外層包含實現細節。

話說回來, Clean Architectue 有一些特性使這成為可能:

  1. 依賴規則
  2. 抽象
  3. 層與層之間的通信

I.依賴規則

依賴規則可以用下圖解釋:

外層應該依賴內層。那三個在紅色框框內的箭頭表示依賴。與其使用“依賴”,也許使用“看見”、“知道”、“了解”這類術語更好。在這些術語中,外層看見,知道,了解內層,但內層看不見,也不知道,更不了解外層。正如我們先前所說,內存包含業務邏輯,外層包含實現細節。遵循依賴規則,業務邏輯既看不到,也不知道,更不了解實現細節。這正是我們努力想要做到的。

如何實現依賴規則取決于你。你可以把它們放到不同的包,但小心“內層的”包不要使用“外層的”包。然而,如果有人不知道依賴規則,沒有什么可以阻止他破壞規則。一個更好的方法是把層分離到不同的 Android 模塊(modules,即子項目),并在構建文件(build.grale)中調整依賴,這樣內層就無法依賴外層。

還有值得一提的是,雖然沒人可以阻止你跨層依賴,譬如藍色的層的組件使用紅色的層的組件,但我強烈建議你只訪問相鄰的層的組件。

II.抽象

抽象原則之前已有所暗示。也就是說,當你朝圖中間移動時,東西變得更抽象。 這是有道理的:正如我們所說內層包含業務邏輯,而外層包含實現細節。

甚至可以在多個層之間劃分相同的邏輯組件,如圖所示。 內層定義更抽象的部分,外層定義更具體的部分。

舉個例子說清楚些。我們可以定義一個 “Notifications” 的抽象接口,并將其放到內層,這樣你的業務邏輯需要時可以使用它來向用戶顯示通知。另一方面,我們可以這樣來實現該接口,即使用 Android NotificationManager 顯示通知來實現,并把該實現放到外層。

以這種方式,業務邏輯可以使用這樣的功能 —— 通知(在我們的例子中)—— 但它不了解實現細節:實際的通知是如何實現的。此外,業務邏輯甚至不知道實現細節的存在。來看下面這張圖片:

當將抽象規則和依賴規則組合在一起時,結果是使用通知的抽象業務邏輯既不會看到,也不會知道,更不會了解使用 Android NotificationManager 的具體實現。這很好,因為我們可以在業務邏輯毫不知情的情況下切換具體實現。

讓我們把這種規則組合和標準的三層架構簡單對比下,看看它們各自的抽象和依賴是怎樣的以及如何工作的。

在圖中,你可以看到,標準三層架構的所有依賴最終都傳到數據庫。也就是說,抽象和依賴并不匹配。在邏輯上,業務層應該是 app 的中心,但它卻不是,因為依賴朝向數據庫。

業務層不應該知道數據庫,應該反過來。在 Clean Architecture 中,依賴朝向業務層(內層),并且抽象也提升到業務層,因此它們很好地匹配。

這是重要的,因為抽象是理論,依賴是實踐。抽象是 app 的邏輯布局,依賴關系是(組件)如何實際組合在一起。在 Clean Architecture 中,這兩者是匹配的。而在標準三層架構中則不然,如果你不小心,很容易導致各種邏輯上的不一致和混亂。

III.層與層之間的通信

現在我們將 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。

原文

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容