Clean Architecture(整洁架构)是由著名软件工程师 Robert C. Martin 提出的一种软件设计理念和架构模式,它通过结构化的方式组织代码,使 Android 应用具备高度可维护性,可测试性和可扩展性。
核心思想
Clean Architecture 的核心原则是依赖规则:源代码依赖只能指向内层,内层不应该知道任何关于外层的东西。
- 领域层(domain)-> 不依赖任何外层,纯 Kotlin/Java,零 Android 框架依赖,只定义业务规则,UseCase 和 Repository 接口。
- 数据层(data)-> 依赖领域层,实现领域层定义的 Repository 接口,但领域层不知其实现。
- 表现层(presentation)-> 依赖领域层,通过 UseCase 调用业务逻辑,不直接依赖数据层。
分层结构
先来看一下 Android 的项目结构
领域层 - 内层
职责与定位:
- 领域层是核心,承载了项目真正的业务价值。
- 定义业务规则和业务模型,包含应用的核心业务逻辑。
- 只关心要做什么,而不是怎么做。
主要组成:
- 实体(model):表示业务核心对象。
- 用例(usecase):封装特定业务行为。
- 仓库接口(repository):定义数据操作的契约,不包含实现。
关键特点:
- 纯 Kotlin/Java 代码,不依赖 Android 框架。
- 不依赖任何第三方库。
- 是整个项目中最稳定的一层。
data class Post(
val id: String,
val name: String
)
class GetPostsUseCase @Inject constructor(
private val postRepository: PostRepository
) {
suspend operator fun invoke(params: HashMap<String, String>): Result<BaseResp<List<Post>>> {
return try {
Result.success(postRepository.getPosts(params))
} catch (e: Exception) {
Result.failure(e)
}
}
}
interface PostRepository {
suspend fun getPosts(params: HashMap<String, String>): BaseResp<List<Post>>
}
数据层 - 中间层
职责与定位:
- 负责数据从哪里来,如何获取。
- 处理数据检索和存储。
- 实现领域层定义的数据访问接口。
主要组成:
- 数据源(datasource):本地数据源或远程数据源
- 仓库实现(repository):协调不同数据源,实现领域层定义的 Repository 接口,处理异常,将技术异常转换为业务可理解的结果。
关键特点:
- 可以自由切换数据来源
- 变化最频繁的一层
- 可根据需求选择不同的数据存储方案
interface PostService {
@GET("/xzj/net/api/example/post")
suspend fun getPosts(@QueryMap params: HashMap<String, String>): BaseResp<List<Post>>
}
class PostRepositoryImpl @Inject constructor(
private val postService: PostService
) : PostRepository {
override suspend fun getPosts(params: HashMap<String, String>): BaseResp<List<Post>> {
return postService.getPosts(params)
}
}
表现层 - 外层
职责与定位:
- 负责 UI 展示和用户交互。
- 将数据转换为用户可感知的界面状态。
- 不包含业务规则,只负责管理 UIState。
主要组成:
- view:视图组件
- ViewModel:通过 usecase 与 领域层交互
- state:UI 状态
关键特点:
- 通常使用 MVVM 或 MVI 模式组织代码。
- 专注于 UI State 管理。
- 可轻松更换用户界面框架或进行界面优化,而不影响业务逻辑。
data class PostListState(
val posts: List<Post> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null
)
@HiltViewModel
class PostListViewModel @Inject constructor(
private val getPostsUseCase: GetPostsUseCase
) : ViewModel() {
private val _state = MutableStateFlow(PostListState())
val state: StateFlow<PostListState> = _state.asStateFlow()
init {
loadPosts()
}
private fun loadPosts() = viewModelScope.launch {
_state.update { it.copy(isLoading = true) }
val params = hashMapOf(
"param1" to "param1",
"param2" to "param2"
)
getPostsUseCase(params).onSuccess { posts ->
_state.update { it.copy(posts = posts.data, isLoading = false) }
}.onFailure { error ->
_state.update { it.copy(error = error.message, isLoading = false) }
}
}
}
@Composable
fun PostListScreen(viewModel: PostListViewModel = hiltViewModel()) {
val state by viewModel.state.collectAsState()
when {
state.isLoading -> CircularProgressIndicator()
state.error != null -> Text("Error: ${state.error}")
else -> LazyColumn {
items(state.posts) { post ->
Text(post.name, fontSize = 20.sp, color = Color.Red)
}
}
}
}
Hilt 通过 @HiltViewModel 和 hiltViewModel() 自动处理 ViewModel 的依赖注入。在 Compose 中使用 hiltViewModel() 获取 ViewModel 实例,确保 ViewModel 及其依赖被正确初始化。
Hilt 依赖注入配置
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Singleton
@Provides
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Singleton
@Provides
fun providePostService(retrofit: Retrofit): PostService {
return retrofit.create(PostService::class.java)
}
}
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Singleton
@Provides
fun providePostRepository(
postService: PostService
): PostRepository {
return PostRepositoryImpl(postService)
}
@Provides
fun provideGetPostsUseCase(
postRepository: PostRepository
): GetPostsUseCase {
return GetPostsUseCase(postRepository)
}
}
意义何在
看到这,你们可能要吐槽了,实现一个功能要建这么多层,这么多类,代码量多这么多,不是徒增工作量吗?真的有意义吗?
首先,徒增工作量是客观存在的,比如你实现一个简单的功能 - 获取用户信息,你需要创建领域层,数据层,表现层,还有各种接口与接口实现,对比 “一把梭” 写法(Activity 直接调用方法获取数据),代码量可能要翻三倍以上。
德国著名哲学家黑格尔曾说过:存在即合理。所以,Clean Architecture 既然存在,那肯定是有意义的!它的意义就在于:用短期成本换长期收益。
举个需求变更的例子吧,就拿常见的用户信息来说,一开始只要求显示 user.name,但是几天后,产品突然改主意了,VIP 用户得显示 VIP 和名字。
按照 “一把梭” 的写法,规则散落在各个 Composable 中的。
@Composable
fun UserCard(user: User) {
Card {
// 第一处:业务规则直接写死在 UI 里!
Text(text = if (user.isVip) "VIP: ${user.name}" else user.name)
}
}
@Composable
fun UserProfileHeader(user: User) {
Column {
// 第二处!规则重复
Text(text = if (user.isVip) "VIP: ${user.name}" else user.name, style = Title)
}
}
@Composable
fun SearchItem(user: User) {
Row {
// 第三处!改需求时需全局搜索替换
Text(text = if (user.isVip) "VIP: ${user.name}" else user.name)
}
}
如果使用 Clean Architecture 的话,只需修改 domain 层即可,其本身就是规则的集中管理。
class GetDisplayNameUseCase { //规则唯一源头!
operator fun invoke(user: User): String =
if (user.isVip) "VIP: ${user.name}" else user.name
// 只改这里!
}
只需修改 UseCase,其余都不用修改,简直不要太优雅。
@HiltViewModel
class UserViewModel @Inject constructor(
private val getDisplayName: GetDisplayNameUseCase // 依赖注入
) : ViewModel() {
// ViewModel 只负责调用规则,不包含规则本身。
fun getDisplayName(user: User) = getDisplayName(user)
}
@Composable
fun UserCard(user: User, viewModel: UserViewModel = hiltViewModel()) {
Card {
// Composable 纯粹只关注显示什么,不关心为什么这样显示。
Text(text = viewModel.getDisplayName(user))
}
}
@Composable
fun UserProfileHeader(user: User, viewModel: UserViewModel = hiltViewModel()) {
Text(text = viewModel.getDisplayName(user), style = Title)
}
@Composable
fun SearchItem(user: User, viewModel: UserViewModel = hiltViewModel()) {
Text(text = viewModel.getDisplayName(user))
}
下次产品再说加什么其他信息的时候,你只需微笑打开 UseCase 即可,而不用翻遍所有 Composable。
但也不是所有项目都该用,而是该用时必须用!对于大型且需持续迭代的项目,架构治理是必然要求,随着代码量增加,改动成本不断上升,所以比较适用于 “中大型”,“团队协作”,“高可靠性要求” 的项目。如果你的项目本身规模不大,也不需要花额外精力治理架构,对于小规模项目,引入 Clean Architecture 可能会增加开发成本,所以不适用。
架构不是炫技,而是为未来的自己和团队减负,我们不能 “为了架构而架构”,若项目注定 “一次性”,请大胆简化,效率优先,若项目需 “活下去、长出来”,就得接受短期成本,投资长期健康。Clean Architecture 的精髓就在于它不承诺 “写得更快”,但承诺 “改得更稳”。