- 原文地址:Kotlin Clean Architecture
- 原文做者:Rakshit jain
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:JasonWu111
- 校對者:yangxy81118
強大的基礎架構對於一個應用擴展和知足用戶羣體的指望來講是很是重要的。我有一個用新更新和優化的 API 結構來替換舊 API 的任務,爲了整合這種更改,我必定程度地重寫了整個應用。html
爲何?由於代碼與其響應的數據模型(data models)深度耦合。此次,我不想一遍又一遍地犯一樣的錯誤。爲了解決這個問題,我使用了 Clean 架構。在一開始會有點痛苦,但對於具備許多功能和 SOLID 方法的大型應用來講多是最佳選擇。讓咱們試着帶着疑問去看架構的每一個層面,而後分解成更簡單的點。前端
這個架構是由 Robert C. Martin(Uncle Bob)在 clean code blog 中於 2012 年提出的。java
「整潔的代碼老是看起來像是由在乎它的人來寫的。」react
— Michael Feathersandroid
Domain 層: 將執行獨立於任何層級的業務邏輯,而且只是一個沒有 Android 相關依賴的純 kotlin 包。ios
Data 層: 經過實現 Domain 層的公開接口,將應用所需的數據分配給 Domain 層。git
Presentation 層: 將包括 Domain 層和 Data 層,而且是 Android 特定的,用於執行 UI 邏輯。github
這將是三個層級中最通用的一個。它將 Presentation 層和 Data 層鏈接起來,並執行應用相關的業務邏輯。數據庫
用例是應用邏輯執行程序。正如名稱所示,每一個功能均可以有其獨立的用例。建立更加精細的用例能夠被更頻繁地複用。後端
class GetNewsUseCase(private val transformer: FlowableRxTransformer<NewsSourcesEntity>,
private val repositories: NewsRepository): BaseFlowableUseCase<NewsSourcesEntity>(transformer){
override fun createFlowable(data: Map<String, Any>?): Flowable<NewsSourcesEntity> {
return repositories.getNews()
}
fun getNews(): Flowable<NewsSourcesEntity>{
val data = HashMap<String, String>()
return single(data)
}
}
複製代碼
此用例返回的是可根據所需觀察者進行修改的 Flowable 類型。它有兩個參數。其中之一是 transformers 或 ObservableTransformer,它控制執行邏輯的線程和另外的參數 repository,是 Data 層的接口。若是有任何的數據必須傳遞給 Data 層,則可使用 HashMap。
它指定了由 Data 層實現的用例所需的功能。
該層級負責提供應用所需的數據。Data 層應該設計任何應用均可以重複使用而無需在其展現邏輯中進行修改的數據。
API 提供遠程網絡實現。任何網絡庫均可以集成到這裏,如 retrofit、volley 等。一樣,DB 提供本地數據庫實現。
class NewsRepositoryImpl(private val remote: NewsRemoteImpl,
private val cache: NewsCacheImpl) : NewsRepository {
override fun getLocalNews(): Flowable<NewsSourcesEntity> {
return cache.getNews()
}
override fun getRemoteNews(): Flowable<NewsSourcesEntity> {
return remote.getNews()
}
override fun getNews(): Flowable<NewsSourcesEntity> {
val updateNewsFlowable = remote.getNews()
return cache.getNews()
.mergeWith(updateNewsFlowable.doOnNext{
remoteNews -> cache.saveArticles(remoteNews)
})
}
}
複製代碼
在 Repository 中,咱們有本地、遠程或任何類型的數據提供程序的實現,而上面的類 NewsRepositoryImpl.kt 實現了 Domain 層公開的接口。它充當 Data 層的單一訪問點。
Presentation 層提供應用的 UI 實現。它不作別的事,只執行沒有邏輯的指令。該層內部實現了 MVC、MVP、MVVM、MVI 等架構。全部的鏈接工做都在本層。
DI 文件夾實現了在應用開始時注入全部的依賴項,如網絡相關、View Models、用例等。可使用 dagger、kodein、koin 或只使用服務定位器模式(service locator pattern)實現 Android 中的 DI。它只取決於應用自己,如對於複雜的應用,DI 可能很是有用。我選擇 koin 只是由於它比 dagger 更容易理解和實現。
根據 Android ViewModel 文檔:
以生命週期的方式存儲和管理 UI 相關數據。它容許數據在配置更改(例如屏幕旋轉)後繼續存活。
class NewsViewModel(private val getNewsUseCase: GetNewsUseCase,
private val mapper: Mapper<NewsSourcesEntity, NewsSources>) : BaseViewModel() {
companion object {
private val TAG = "viewmodel"
}
var mNews = MutableLiveData<Data<NewsSources>>()
fun fetchNews() {
val disposable = getNewsUseCase.getNews()
.flatMap { mapper.Flowable(it) }
.subscribe({ response ->
Log.d(TAG, "On Next Called")
mNews.value = Data(responseType = Status.SUCCESSFUL, data = response)
}, { error ->
Log.d(TAG, "On Error Called")
mNews.value = Data(responseType = Status.ERROR, error = Error(error.message))
}, {
Log.d(TAG, "On Complete Called")
})
addDisposable(disposable)
}
fun getNewsLiveData() = mNews
}
複製代碼
所以,ViewModel 會保留有關配置更改的數據。在 MVP 中,Presenter 使用接口綁定到 view,這會變得難以測試,但在 ViewModel 中,因爲架構感知組件(architectural aware components)而沒有接口。
Base View Model 使用 CompositeDisposable 來添加全部的 observables 對象,並在生命週期的 @OnCleared 中移除它們。
data class Data<RequestData>(var responseType: Status, var data: RequestData? = null, var error: Error? = null)
enum class Status { SUCCESSFUL, ERROR, LOADING }
複製代碼
數據 wrapper 類做爲輔助類用於 LiveData,以便 view 瞭解數據請求的狀態,即它是否已開始、成功或任何有關數據的狀態。
每一個層都有本身特定於該包的 實體類(entities)。Mapper 用於將一個層的實體類轉換爲另外一個層的實體類。咱們爲每一個層設置了不一樣的實體類,以便該層變得絕對獨立,而且只將所需的數據傳遞給後續的層。
本文差很少要結束了,若是我錯過了任何內容,請告訴我。讓我總結一下:
基礎架構定義了應用程序的一致性。誠然,選擇什麼架構也是基於應用來的,可是爲何不提早選擇最合適的架構呢,如可擴展的,強大的,可測試的,這樣你就沒必要在將來面對痛苦。
感謝閱讀本文 :)
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。