Clean Architecture
Clean Architecture 由Robert C. Martin(Uncle Bob)发布于2012年 :
http://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
目前被作为项目结构组织的最佳范式被广泛熟知,连Android官方的MVVM示例项目中也有所使用。Clean架构很好地实践了SLOID原则,具有以下优点
- 分层设计贯彻了关注点分离
- 高度抽象,适用于各类型的业务
- 各层之间保持松耦合
- 便于测试
“Clean code always looks like it was written by someone who cares.”
— Michael Feathers
本文结合具体的Sample,介绍在Android+Kotlin项目中如何落地Clean架构:
https://github.com/rakshit444/news-sample-app
分层设计
- Domain layer(领域层):
承担业务逻辑,一般与平台无关,所由纯Kotlin实现,不应该依赖Android相关的SDK - Data layer(数据层):
为领域层服务,通过实现领域层的接口为其获取数据,一般也是纯Kotlin代码 - Presentation layer(表示层):
依赖领域层获取数据,并通过android实现UI渲染逻辑
Domain Layer 领域层
这将是三层中最通用的一层。它将连接表示层和数据层,用来执行具体业务逻辑
UseCases
UseCases是一段独立逻辑承载和执行单元,UseCase的划分粒度越粗就越抽象,越是利于复用,例如GetNewsUseCase:
//GetNewsUseCase.kt
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)
}
}
GetNewsUseCase返回一个Flowable供UI层订阅。两个参数
- transformer:决定逻辑执行在哪个线程
- repositories:提供接口给数据层获取数据
Repositories
提供给数据层的interface
Data Layer 数据层
数据层用来请求数据,数据层的逻辑应该具有可复用性,相对于UI层更加稳定
api & db
api和db分别提供远程和本地的数据源,android中可能体现为retrofit和sqllite等
repository
repository中是数据层对于领域层的接口实现,例如:
//NewsRepositoryImpl.kt
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中封装了对数据源的获取方式,请求的数据可以来自本地或远程,但对外不必暴露具体细节
Presenter Layer 表现层
表现层负责UI渲染的逻辑。可以等同于各种MVX框架中的V层,不应承担任何业务逻辑
di
di中存放各种表现层的依赖对象,例如一些工具类,ViewModel、UseCase等等。di一般可以使使用dagger、koin等di框架实现。本例中使用的是koin,因为它相对于dagger更加简单,适合规模不大的项目使用
ViewModel
Store and manage UI-related data in a lifecycle conscious way. It allows data to survive configuration changes such as screen rotations.
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存储UI的状态。Data作为一个统一的Wrapper,处理数据请求状态
data class Data<RequestData>(var responseType: Status, var data: RequestData? = null, var error: Error? = null)
enum class Status { SUCCESSFUL, ERROR, LOADING }
MVVM相对于MVP中,不再重度依赖接口,且能感知生命周期。
这里BaseViewModel
通过CompositeDisposable 收集所有的订阅,然后再onCleared中统一释放
数据流向
Android项目中的Clean架构,整体数据流向如下图:相对于普通的MVVM,相当于把ViewModel的职责进一步细分成多个UseCase,避免ViewModel的膨胀