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

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

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

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

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

掌控你的領域(domain)

當你打開項目時找前,你應該已經知道這個 app 是做什么的,與技術無關柏腻。其它一切都是實現細節(jié)纸厉。譬如系吭,持久化就是一個細節(jié)五嫂。定義接口,創(chuàng)建一個快速的粗糙的內存內(in-memory)實現肯尺,不要想太多沃缘,直到完成業(yè)務。然后你可以決定怎樣真正地持久化數據则吟。數據庫槐臀,網絡,兩者結合氓仲,文件系統(tǒng) —— 或者仍然保留在內存中水慨,或者結果你根本不需要持久化【纯福總之一句話:內層包含業(yè)務邏輯晰洒,外層包含實現細節(jié)。

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

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

I.依賴規(guī)則

依賴規(guī)則可以用下圖解釋:

外層應該依賴內層谍珊。那三個在紅色框框內的箭頭表示依賴。與其使用“依賴”急侥,也許使用“看見”砌滞、“知道”侮邀、“了解”這類術語更好。在這些術語中贝润,外層看見绊茧,知道,了解內層题暖,但內層看不見按傅,也不知道,更不了解外層胧卤。正如我們先前所說唯绍,內存包含業(yè)務邏輯,外層包含實現細節(jié)枝誊。遵循依賴規(guī)則况芒,業(yè)務邏輯既看不到,也不知道叶撒,更不了解實現細節(jié)绝骚。這正是我們努力想要做到的。

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

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

II.抽象

抽象原則之前已有所暗示。也就是說萌京,當你朝圖中間移動時雁歌,東西變得更抽象。 這是有道理的:正如我們所說內層包含業(yè)務邏輯枫夺,而外層包含實現細節(jié)将宪。

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

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

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

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

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

在圖中仗谆,你可以看到,標準三層架構的所有依賴最終都傳到數據庫跨释。也就是說胸私,抽象和依賴并不匹配厌处。在邏輯上鳖谈,業(yè)務層應該是 app 的中心,但它卻不是阔涉,因為依賴朝向數據庫缆娃。

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

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

III.層與層之間的通信

現在我們將 app 分模塊,將所有內容分開关翎,將業(yè)務邏輯放在我們 app 的中心扛门,并在外層實現細節(jié),一切看起來都很棒纵寝。 但是你可能很快遇到一個有趣的問題尖飞。

如果你的 UI 是一個實現細節(jié),網絡是一個實現細節(jié)店雅,業(yè)務邏輯在中間政基,那么我們如何從互聯(lián)網獲取數據,經過業(yè)務邏輯闹啦,然后發(fā)送到界面沮明?

業(yè)務邏輯在中間,應該協(xié)調網絡和界面窍奋,但它甚至不知道兩者的存在荐健。這是一個關于通信和數據流的問題。

我們希望數據能夠從外層流向內層琳袄,反之亦然江场,但依賴規(guī)則不允許。 讓我們舉個最簡單的例子窖逗。

我們只有兩層址否,綠色和紅色的。綠色的是外層碎紊,它知道紅色的佑附,紅色的是內層,它只知道自己仗考。我們希望數據從綠色流向紅色音同,然后折回綠色。該解決方案先前已經暗示過了秃嗜,看下圖:

圖的右邊部分顯示了數據流权均。數據源于 Controller顿膨,經過 UseCase(或者替換成你選擇的組件)的輸入端口,然后通過 UseCase 本身叽赊,最后通過 UseCase 輸出端口發(fā)送到 Presenter虽惭。

圖的主要部分(左邊)的箭頭表示組合和繼承 —— 組合用實心箭頭表示,繼承用空心箭頭表示蛇尚。組合也被稱作 has-a 關系芽唇,繼承被稱作 is-a 關系。圓圈中的 “I” 和 “O” 表示輸入和輸出端口取劫〈殷裕可以看到,定義在綠色層中的 Controller谱邪,擁有一個(has-a)定義在紅色層中的輸入端口炮捧。UseCase(齒輪,業(yè)務邏輯惦银,現在不重要)是一個(is-a)(或實現)輸入端口咆课,并且擁有一個(has-a)輸出端口。最后扯俱,定義在綠色層中的 Presenter 實際上是一個(is-a)定義在紅色層的輸出端口书蚪。

現在,我們可以將其與數據流匹配迅栅。Controller 擁有一個輸入端口 —— 擁有一個指向它的引用殊校。它調用輸入端口的一個方法,這樣數據就從 Controller 流到輸入端口读存。但輸入端口是一個接口为流,而它的實際實現是 UseCase。也就是說让簿,它調用 UseCase 的一個方法敬察,這樣數據就流向了 UseCase。UseCase 執(zhí)行某些操作尔当,并希望將數據發(fā)送回來莲祸。它擁有輸出端口的一個引用 —— 輸出端口定義在同一層 —— 因此它可以調用上面的方法。因此居凶,數據流向輸出端口虫给。最后 Presenter 是藤抡,或者實現了輸出端口侠碧,這是魔法的一部分。因為它實現了輸出端口缠黍,數據實際上流到它那了弄兜。

巧妙的是,UseCase 只知道它的輸出端口,世界在此停止(意指數據流到此結束)替饿。Presenter 實現了它(輸出端口)语泽,實際上它可以被任何對象實現,因為 UseCase 不知道或不關心這些视卢,它只清楚其層內的一畝三分地踱卵。可以看到据过,通過結合組合和繼承惋砂,我們可以使數據流向兩個方向,盡管內層并不知道它們在和外部世界通信绳锅。瞄一眼下圖:

可以看到西饵,和依賴箭頭一樣,has-a 和 is-a 箭頭也指向中間鳞芙。這是符合邏輯的眷柔。根據依賴規(guī)則,這是唯一可行的方法原朝。外層可以看到內層驯嘱,但不能反過來。唯一復雜的部分是喳坠,is-a 關系盡管指向了中間宙拉,卻反轉了數據流。

請注意丙笋,定義輸入和輸出端口是內層自己的職責谢澈,因此外層可以使用它們與其建立通信。我說過御板,這個解決方案先前已經暗示過锥忿,而且已經有了。那個講解抽象的通知例子怠肋,也是這種通信的一個例子敬鬓。我們在內層定義了一個通知接口,業(yè)務邏輯可以用來向用戶顯示通知笙各,但是我們在外層也定義一個實現钉答。在這種情況下,通知接口是業(yè)務邏輯的輸出端口杈抢,用來和外部世界(在本例中数尿,就是和具體的實現)通信。你不需要把你的類命名為 FooOutputPort 或者 BarInputPort惶楼,我們命名端口只是為了解釋理論右蹦。

總結

那么诊杆,它是過度復雜,過度費解的過度工程嗎何陆?好吧晨汹,當你習慣了,它就簡單贷盲。并且這是必要的淘这。它允許我們使得好的抽象/依賴實際匹配真實世界的通信和工作。也許這一切都提醒你不過是空中樓閣:美麗巩剖,理論上優(yōu)雅慨灭,但過于復雜,我們仍然不知它是否有效球及,但在我們的案例中氧骤,它確實有效。

這就是本系列的第二部分吃引。最后筹陵,第三部分,畢竟我們已經了解了理論和架構镊尺,將講解所有你需要了解的那些圖上的標簽朦佩。換句話說,分離的組件庐氮。我們將向你展示一個真實的應用于 Android 的 Clean Architecture语稠。

原文

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市弄砍,隨后出現的幾起案子仙畦,更是在濱河造成了極大的恐慌,老刑警劉巖音婶,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慨畸,死亡現場離奇詭異,居然都是意外死亡衣式,警方通過查閱死者的電腦和手機寸士,發(fā)現死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碴卧,“玉大人弱卡,你說我怎么就攤上這事∽〔幔” “怎么了婶博?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長界弧。 經常有香客問我凡蜻,道長搭综,這世上最難降的妖魔是什么垢箕? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任划栓,我火速辦了婚禮,結果婚禮上条获,老公的妹妹穿的比我還像新娘忠荞。我一直安慰自己,他們只是感情好帅掘,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布委煤。 她就那樣靜靜地躺著,像睡著了一般修档。 火紅的嫁衣襯著肌膚如雪碧绞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天吱窝,我揣著相機與錄音讥邻,去河邊找鬼。 笑死院峡,一個胖子當著我的面吹牛兴使,可吹牛的內容都是我干的。 我是一名探鬼主播照激,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼发魄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了俩垃?” 一聲冷哼從身側響起励幼,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎口柳,沒想到半個月后赏淌,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡啄清,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年六水,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辣卒。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡掷贾,死狀恐怖,靈堂內的尸體忽然破棺而出荣茫,到底是詐尸還是另有隱情想帅,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布啡莉,位于F島的核電站港准,受9級特大地震影響旨剥,放射性物質發(fā)生泄漏。R本人自食惡果不足惜浅缸,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一轨帜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧衩椒,春花似錦蚌父、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至阁将,卻和暖如春膏秫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背做盅。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工缤削, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人言蛇。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓僻他,卻偏偏與公主長得像,于是被迫代替她去往敵國和親腊尚。 傳聞我的和親對象是個殘疾皇子吨拗,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內容