【譯】Android架構(gòu)之引入clean 架構(gòu)

原文鏈接:Architecting Android…The clean way?

在過去的幾個(gè)月里,在Tuenti和同事@pedro_g_s@flipper83(順帶一提,這兩個(gè)都是Android開發(fā)高手)探討之后,我覺得是時(shí)候?qū)懸黄P(guān)于構(gòu)建Android應(yīng)用架構(gòu)方面的文章了。

本文的目的在于告訴你我過去幾個(gè)月腦中的一些想法,還有我研究和實(shí)現(xiàn)后總結(jié)的一些資料。

Getting Started

我們知道編寫高質(zhì)量的軟件很困難也很復(fù)雜:它不僅僅是符合需求,還要有魯棒性、可維護(hù)、可測試和足夠靈活,以適應(yīng)代碼增長和改變的需要。因此,我們提出了“the clean architecture“(清爽架構(gòu))的概念,它對(duì)于開發(fā)任何軟件應(yīng)用都是一個(gè)絕佳的方案。

這個(gè)概念很簡單:clean architecture主張通過一系列的實(shí)踐,來構(gòu)建一個(gè)具備下面特性的系統(tǒng):

  • Independent of Frameworks(框架獨(dú)立)
  • Testable(可測試)
  • Independent of UI(用戶界面獨(dú)立)
  • Independent of Database(數(shù)據(jù)庫獨(dú)立)
  • Independent of any external agency(所有外部代理獨(dú)立)

這并不是說一定只用這4層(如圖所示),因?yàn)檫@張?jiān)韴D只是要讓你考慮Dependency Rule(依賴規(guī)則):源代碼中的依賴只能指向里面,內(nèi)圈代碼必須對(duì)外圈的一無所知。

下面有一些相關(guān)的詞匯能讓你更好地熟悉和理解該方法:

  • Entities:應(yīng)用的業(yè)務(wù)對(duì)象所在
  • Use Cases:協(xié)調(diào)數(shù)據(jù)從entities中流進(jìn)流出。也可以叫Interactors。(備注:用例,就是一系列的步驟,典型地說就是用戶為達(dá)到一種目的,與系統(tǒng)進(jìn)行的交互)
  • Interface Adapters:轉(zhuǎn)換數(shù)據(jù)以方便use cases和entities使用。Presenters和Controllers都屬于此處
  • Frameworks and Drivers:細(xì)節(jié)處理,比如UI、tools、frameworks等等

更多更棒的解析,參見這篇文章這個(gè)視頻

Our Scenario

我會(huì)從一個(gè)簡單的方案開始講起:一個(gè)簡單的app,顯示一個(gè)朋友或用戶列表,數(shù)據(jù)從云端獲取;當(dāng)點(diǎn)擊任意一項(xiàng),會(huì)打開一個(gè)新界面,顯示用戶詳情。

我錄制了一個(gè)視頻,這樣你就可以對(duì)我談?wù)摰臇|西有個(gè)大概了。

Android Architecture

我們的目標(biāo)是:通過讓業(yè)務(wù)邏輯對(duì)外部世界一無所獲來進(jìn)行解耦,這樣,業(yè)務(wù)邏輯就可以毫無依賴地使用外部元素進(jìn)行測試了。為了達(dá)到該目的,我的解決方案是劃分項(xiàng)目成3個(gè)不同的層次,每個(gè)層都有各自的功能,并且各自獨(dú)立工作。

值得一提的是,每個(gè)層使用各自的數(shù)據(jù)模型(data model),因?yàn)檫@種獨(dú)立是可達(dá)的(你可以在代碼中有一個(gè)data mapper,它用來完成數(shù)據(jù)的轉(zhuǎn)換,如果你不想在整個(gè)應(yīng)用中交叉使用models的話是要支付一定的代價(jià)的)

看看下面的圖就知道了:

clean_architecture_android

注意:我沒有使用任何外部類庫(除了使用gson來解析json數(shù)據(jù),使用junit、mockito、robolectric和espresso來測試)。這是為了讓示例代碼更清晰點(diǎn)。不管如何,你可以毫不猶豫地添加ORM進(jìn)行數(shù)據(jù)存儲(chǔ)或者任何依賴注入框架或者任何你熟悉的工具和類庫,那樣你的生活很更美好。(記住,不要重新造輪子)

Presentation Layer

這里放置的是關(guān)聯(lián)視圖和動(dòng)畫相關(guān)的邏輯代碼。它不僅僅可以使用MVP模式(Model View Presenter),也可以使用MVC或MVVM。關(guān)于這點(diǎn)我不會(huì)展開來說。在這里,fragments和activities都只是views,里面沒有任何邏輯,當(dāng)然除了UI邏輯。所有需要渲染或展示的東西都在這里進(jìn)行。

本層的Presentersinteractors(use cases)構(gòu)成,在UI線程之外的新線程進(jìn)行工作,然后通過回調(diào)將數(shù)據(jù)提供給view進(jìn)行渲染或展示。

clean_architecture_mvp

如果你在找一個(gè)使用MVP和MVVM的酷酷的案例,推薦看看我朋友 Pedro Gómez 寫的

Effective Android UI

Domain Layer

這里是業(yè)務(wù)邏輯層:所有的邏輯都在這里進(jìn)行。環(huán)視整個(gè)android項(xiàng)目,你會(huì)發(fā)現(xiàn)所有的interactors(use cases)都是在這里實(shí)現(xiàn)的。

該層屬于純java模塊,沒有一丁點(diǎn)的android依賴。所有的外部組件(components)都是以接口形式跟business objects聯(lián)系。

clean_architecture_domain
clean_architecture_domain

Data Layer

應(yīng)用所需的所有數(shù)據(jù)都來自該層,通過一個(gè)UserRepository的實(shí)現(xiàn)(其接口在domain layer),使用一個(gè)存儲(chǔ)庫模式(Repository Pattern),使用這樣的策略:通過一個(gè)工廠依賴于具體的條件,獲取不同的數(shù)據(jù)。

例如,當(dāng)需要通過id獲取user,如果user已經(jīng)存在于緩存中,那么將會(huì)從磁盤中緩存的數(shù)據(jù)源中獲取;否則,從云端獲取,然后再緩存到本地磁盤。

這一切背后的理念是數(shù)據(jù)源對(duì)于客戶端透明,就是說客戶端不用關(guān)心數(shù)據(jù)是從內(nèi)存、磁盤還是云端獲取的,只要知道數(shù)據(jù)終會(huì)被獲取就行了。

clean_architecture_data

注意:在代碼方面,我已經(jīng)實(shí)現(xiàn)了一個(gè)非常簡單和原始的磁盤緩存,使用的是文件系統(tǒng)和android preferences,僅僅是出于學(xué)習(xí)目的。再次提醒,如果已有類庫可以搞定需求就不要重復(fù)造輪子

Error Handling

這個(gè)話題仍在探討中,如果你可以在這里分享下你的解決方案的話就更棒了。

我的策略是使用回調(diào),因此,假如在data repository有事件發(fā)生,回調(diào)接口有兩個(gè)方法onResponse()和onError()來進(jìn)行處理。最后一個(gè)使用了一個(gè)叫“ErrorBundle”的包裝類來封裝異常:這種方法帶來了一些困境,因?yàn)榇嬖谥粭l直達(dá)顯示層的回調(diào)鏈。代碼可讀性難以保證。

另一方面,我已經(jīng)實(shí)現(xiàn)了一個(gè)事件總線系統(tǒng),如果有錯(cuò)誤發(fā)生會(huì)拋出一個(gè)事件。這個(gè)解決方案類似于GOTO。我的觀點(diǎn)是,如果你訂閱了多個(gè)事件而沒有緊接著處理好的話,你會(huì)丟失一些事件。

Testing

關(guān)注于測試,我根據(jù)各個(gè)層的特點(diǎn)選擇了幾個(gè)解決方案:

  • Presentation Layer:使用android instrumentation和espresso進(jìn)行綜合測試和功能測試
  • Domain Layer:使用JUnit+mockito進(jìn)行單元測試
  • Data Layer:Robolectric(因?yàn)樵搶佑衋ndroid依賴)+ JUnit + mockito進(jìn)行綜合和單元測試

Show me the code

我知道你一定想知道代碼放哪里了,對(duì)吧?諾,這里有個(gè)github鏈接,我代碼都放上面了。關(guān)于其中的文件結(jié)構(gòu),我要說幾句,不同的層表示的使用的模塊如下:

  • presentation:屬于android 模塊,代表presenttation層
  • domain:屬于java模塊,沒有android依賴
  • data:屬于android模塊,數(shù)據(jù)源
  • data-test:data層的測試代碼。由于使用Robolectric的一些限制,我不得不獨(dú)立出一個(gè)java模塊

Conclusion

正如Uncle Bob說的 "Architecture is About Intent, not Frameworks" ,我完全同意這種說法。當(dāng)然,達(dá)到目的的途徑有很多且各異(不同的實(shí)現(xiàn)),我們每天都面臨著許許多多的挑戰(zhàn)。但是,通過使用該技術(shù),你可以確保你的應(yīng)用:

  • 易于維護(hù)
  • 易于測試
  • 高內(nèi)聚
  • 低耦合

總而言之,我強(qiáng)烈推薦你試一下,然后把你的結(jié)果和經(jīng)歷分享出來,包括你發(fā)現(xiàn)的其他更好的方法:我們知道持續(xù)改進(jìn)是一件非常棒喝積極的事。

我希望本文對(duì)于有所裨益,并且我一如既往地歡迎任何反饋。

Source code

  1. Clean architecture github repository – master branch
  2. Clean architecture github repository – releases

Further reading:

  1. Architecting Android..the evolution
  2. Tasting Dagger 2 on Android
  3. The Mayans Lost Guide to RxJava on Android
  4. It is about philosophy: Culture of a good programmer

Links and Resources

  1. The clean architecture by Uncle Bob
  2. Architecture is about Intent, not Frameworks
  3. Model View Presenter
  4. Repository Pattern by Martin Fowler
  5. Android Design Patterns Presentation
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,813評(píng)論 25 708
  • afinalAfinal是一個(gè)android的ioc,orm框架 https://github.com/yangf...
    passiontim閱讀 15,489評(píng)論 2 45
  • 原文鏈接:Architecting Android…The clean way? 在過去的幾個(gè)月中我與公司T...
    artshell閱讀 1,080評(píng)論 0 1
  • 前幾天小羅子的生日,他的幾個(gè)北方朋友把他灌得搖搖晃晃,他們喝了很多酒,我不勝酒力,也沒法和不熟的人打得熱火朝天,十...
    病蘇閱讀 390評(píng)論 0 0
  • 我是誰?天啊,問完自己居然卡殼了。確實(shí)不容易回答啊!而且這個(gè)問題上百度問專家也沒有參考答案呀。好吧,暫且先敲著鍵盤...
    VvAngel閱讀 220評(píng)論 0 1