1. 问题现象:UseCase泛滥的架构困境
在Android Clean Architecture实践中,我见过最典型的反模式就是UseCase的滥用。很多团队机械地遵循"每个业务操作一个UseCase"的原则,最终导致项目中充斥着数百个零散的UseCase类。这种看似规范的架构,实际却带来了三个致命问题:
- 代码导航困难:当需要修改某个业务流时,开发者需要在十几个UseCase之间跳转
- 依赖关系混乱:UseCase之间相互调用形成网状结构,违背了单向依赖原则
- 测试维护成本高:每个简单操作都需要为对应的UseCase编写测试用例
典型案例:某电商App的购物车模块,竟然定义了37个UseCase,包括
AddItemUseCase、RemoveItemUseCase、UpdateQuantityUseCase等,每个类只有不到10行有效代码。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题根源:对Clean Architecture的误解
2.1 UseCase的本质误读
Clean Architecture原意中,UseCase应该代表完整的用户场景,而非细碎的操作步骤。比如"完成订单支付"是一个合理的UseCase,而"更新支付状态"、"扣除库存"这些应该是其内部实现细节。
常见错误认知:
- 认为UseCase粒度越细越好
- 把UseCase当作DAO的二次封装
- 为每个CRUD操作创建独立UseCase
2.2 架构分层的形式主义
很多团队机械照搬三层架构(UI-Domain-Data),却忽略了分层的核心目的是:
- 隔离变化(业务逻辑不依赖具体实现)
- 降低耦合(模块间明确依赖方向)
- 提升可测试性(各层可独立测试)
当UseCase变成简单的"传话筒"(只是调用Repository方法),这种分层就失去了意义。
3. 解决方案:合理的UseCase设计模式
3.1 功能聚合原则
一个好的UseCase应该:
- 对应一个完整的用户故事(User Story)
- 包含完整的业务逻辑闭环
- 处理所有相关异常情况
kotlin复制// 反例:碎片化UseCase
class GetUserUseCase @Inject constructor(
private val userRepo: UserRepository
) {
suspend operator fun invoke(userId: String) = userRepo.getUser(userId)
}
// 正例:聚合业务场景
class PlaceOrderUseCase @Inject constructor(
private val orderRepo: OrderRepository,
private val inventoryRepo: InventoryRepository,
private val paymentService: PaymentService
) {
suspend operator fun invoke(order: Order): Result<OrderReceipt> {
return try {
// 验证库存
inventoryRepo.checkAvailability(order.items)
// 创建支付
val payment = paymentService.createPayment(order)
// 持久化订单
orderRepo.saveOrder(order.copy(status = PAID))
// 更新库存
inventoryRepo.updateStock(order.items)
Result.success(OrderReceipt(order, payment))
} catch (e: Exception) {
// 统一错误处理
Result.failure(e)
}
}
}
3.2 依赖管理最佳实践
正确的依赖关系应该是:
code复制ViewModel → UseCase ← Repository
↑
Domain Models
关键约束:
- ViewModel永远不直接访问Repository
- UseCase之间避免相互调用
- 跨UseCase共享逻辑应下沉到Domain层
3.3 测试策略优化
聚合后的UseCase反而更易于测试:
- 一个测试类覆盖完整业务场景
- 可以验证多个Repository的交互顺序
- 异常处理只需测试一次
kotlin复制@Test
fun `placeOrder should fail when inventory insufficient`() = runTest {
// Given
val useCase = PlaceOrderUseCase(
mockOrderRepo,
mockInventoryRepo.apply {
whenever(checkAvailability(any())).thenThrow(InventoryException())
},
mockPaymentService
)
// When & Then
assertFailsWith<InventoryException> {
useCase(TestData.testOrder)
}
}
4. 实操建议:架构演进路线
4.1 现有项目改造步骤
-
审计现有UseCase
- 识别简单的"传话筒"UseCase
- 标记相互调用的UseCase
- 统计各UseCase的调用频次
-
合并同类项
mermaid复制graph LR A[GetUserUseCase] --> B[UserProfileUseCase] C[GetUserAvatarUseCase] --> B D[UpdateUserUseCase] --> B -
重构依赖关系
- 将共享逻辑提取到Domain Service
- 建立清晰的模块边界
- 引入依赖注入框架管理生命周期
4.2 新项目预防措施
-
设计阶段
- 用Event Storming识别核心领域
- 根据用户旅程划分UseCase边界
- 明确各层职责分工
-
开发规范
markdown复制## UseCase编写Checklist - [ ] 对应一个完整用户场景 - [ ] 包含至少3个业务步骤 - [ ] 处理所有已知异常情况 - [ ] 不直接依赖其他UseCase -
架构守护
- 使用ArchUnit进行依赖检查
- 配置Detekt规则限制UseCase粒度
- Code Review时重点关注跨层调用
5. 经验总结:架构设计的平衡艺术
经过多个项目的实践,我发现健康的Android架构应该:
- 适度抽象:UseCase不是越少越好,关键在功能内聚
- 演进式设计:初期可以适当粗粒度,随业务复杂化逐步拆分
- 团队共识:架构规范需要全员理解其背后的设计意图
最成功的案例是一个从300+ UseCase重构到50+的项目:
- 编译时间减少40%
- 代码重复率下降65%
- 新功能开发效率提升30%
记住:架构的终极目标是服务于业务需求,而不是追求形式上的"整洁"。当发现UseCase开始"繁殖",就是时候重新审视领域模型了。
