106. Clean Architecture在Android大型项目中的实战应用

摘要

本文深入探讨Clean Architecture在Android大型项目中的实战应用。通过分析智能家居IoT平台的架构演进,详细阐述Clean Architecture的分层设计、依赖规则、用例设计等核心概念,并提供完整的Kotlin实现方案。文章涵盖从理论到实践的完整路径,包括架构分层、边界定义、数据流转、测试策略等关键环节,为大型项目的架构设计提供可落地的参考方案。

关键词:Clean Architecture、分层架构、依赖倒置、用例设计、领域驱动设计、架构边界


一、Clean Architecture核心理念

1.1 架构目标

Clean Architecture的核心目标是构建可维护、可测试、独立于框架的系统。

Clean Architecture目标

独立于框架

可测试性

独立于UI

独立于数据库

独立于外部代理

业务规则不依赖框架

业务规则可单独测试

UI可轻易替换

数据源可灵活切换

业务规则不知道外部世界

高度解耦的系统

1.2 分层架构

Clean Architecture采用同心圆分层模型,内层不依赖外层。

Clean Architecture分层

Entities
企业业务规则

Use Cases
应用业务规则

Interface Adapters
接口适配器

Frameworks & Drivers
框架与驱动

依赖方向


二、架构分层设计

2.1 Domain层(核心业务层)

Domain层包含企业业务规则和实体,完全独立于Android框架。

实体定义

// domain/model/Device.kt
package com.某品牌.domain.model

/**
 * 设备领域模型
 * 包含核心业务规则,与框架无关
 */
data class Device(
    val id: DeviceId,
    val name: String,
    val type: DeviceType,
    val status: DeviceStatus,
    val capabilities: List<Capability>,
    val metadata: DeviceMetadata
) {
    /**
     * 业务规则:检查设备是否可控制
     */
    fun isControllable(): Boolean {
        return status == DeviceStatus.ONLINE &&
               capabilities.isNotEmpty()
    }

    /**
     * 业务规则:执行能力验证
     */
    fun canExecute(capability: Capability): Boolean {
        return capabilities.contains(capability) &&
               isControllable()
    }

    /**
     * 业务规则:检查是否需要固件升级
     */
    fun needsFirmwareUpdate(latestVersion: String): Boolean {
        return metadata.firmwareVersion < latestVersion
    }
}

/**
 * 值对象:设备ID
 */
@JvmInline
value class DeviceId(val value: String) {
    init {
        require(value.isNotBlank()) { "Device ID cannot be blank" }
    }
}

/**
 * 枚举:设备类型
 */
enum class DeviceType {
    CAMERA,
    DOORBELL,
    LOCK,
    SENSOR,
    LIGHT
}

/**
 * 枚举:设备状态
 */
enum class DeviceStatus {
    ONLINE,
    OFFLINE,
    UPGRADING,
    ERROR
}

/**
 * 设备能力
 */
sealed class Capability {
    data class PowerControl(val maxPower: Int) : Capability()
    data class BrightnessControl(val range: IntRange) : Capability()
    data class MotionDetection(val sensitivity: Float) : Capability()
    data class TwoWayAudio(val sampleRate: Int) : Capability()
}

/**
 * 设备元数据
 */
data class DeviceMetadata(
    val firmwareVersion: String,
    val hardwareVersion: String,
    val manufacturer: String,
    val model: String,
    val serialNumber: String,
    val createdAt: Long,
    val updatedAt: Long
)

领域服务

// domain/service/DeviceAuthorizationService.kt
package com.某品牌.domain.service

/**
 * 设备授权领域服务
 * 处理复杂的设备授权业务逻辑
 */
class DeviceAuthorizationService {

    /**
     * 检查用户是否有权限控制设备
     */
    fun canUserControlDevice(
        user: User,
        device: Device,
        operation: DeviceOperation
    ): AuthorizationResult {
        // 业务规则1:设备必须在线且可控制
        if (!device.isControllable()) {
            return AuthorizationResult.Failure(
                reason = "Device is not controllable"
            )
        }

        // 业务规则2:用户必须是设备所有者或被授权用户
        if (!user.hasAccessTo(device.id)) {
            return AuthorizationResult.Failure(
                reason = "User not authorized"
            )
        }

        // 业务规则3:检查操作权限级别
        val requiredLevel = operation.requiredPermissionLevel()
        val userLevel = user.getPermissionLevel(device.id)

        if (userLevel < requiredLevel) {
            return AuthorizationResult.Failure(
                reason = "Insufficient permission level"
            )
        }

        return AuthorizationResult.Success
    }
}

/**
 * 授权结果
 */
sealed class AuthorizationResult {
    object Success : AuthorizationResult()
    data class Failure(val reason: String) : AuthorizationResult()
}

2.2 Use Case层(应用业务层)

Use Case层定义应用特定的业务规则。

// domain/usecase/GetDeviceListUseCase.kt
package com.某品牌.domain.usecase

/**
 * 获取设备列表用例
 * 编排业务流程,协调多个repository和service
 */
class GetDeviceListUseCase(
    private val deviceRepository: DeviceRepository,
    private val userRepository: UserRepository,
    private val authorizationService: DeviceAuthorizationService
) {
    /**
     * 执行用例
     * @param filter 过滤条件
     * @return 设备列表结果
     */
    suspend operator fun invoke(
        filter: DeviceFilter
    ): Result<List<Device>> = runCatching {
        // 1. 获取当前用户
        val currentUser = userRepository.getCurrentUser()
            ?: throw IllegalStateException("User not logged in")

        // 2. 获取用户有权限的设备列表
        val allDevices = deviceRepository.getDevicesByUser(currentUser.id)

        // 3. 应用过滤条件
        val filteredDevices = allDevices.filter { device ->
            applyFilter(device, filter)
        }

        // 4. 按状态和类型排序
        val sortedDevices = filteredDevices.sortedWith(
            compareByDescending<Device> { it.status == DeviceStatus.ONLINE }
                .thenBy { it.type }
                .thenBy { it.name }
        )

        sortedDevices
    }

    private fun applyFilter(device: Device, filter: DeviceFilter): Boolean {
        if (filter.types.isNotEmpty() && device.type !in filter.types) {
            return false
        }

        if (filter.statuses.isNotEmpty() && device.status !in filter.statuses) {
            return false
        }

        if (filter.searchQuery.isNotBlank()) {
            val query = filter.searchQuery.lowercase()
            if (!device.name.lowercase().contains(query) &&
                !device.metadata.model.lowercase().contains(query)) {
                return false
            }
        }

        return true
    }
}

/**
 * 设备过滤条件
 */
data class DeviceFilter(
    val types: Set<DeviceType> = emptySet(),
    val statuses: Set<DeviceStatus> = emptySet(),
    val searchQuery: String = ""
)

复杂用例示例

// domain/usecase/ControlDeviceUseCase.kt
package com.某品牌.domain.usecase

/**
 * 控制设备用例
 * 包含完整的业务流程:授权、验证、执行、记录
 */
class ControlDeviceUseCase(
    private val deviceRepository: DeviceRepository,
    private val userRepository: UserRepository,
    private val authorizationService: DeviceAuthorizationService,
    private val commandExecutor: DeviceCommandExecutor,
    private val eventRepository: DeviceEventRepository
) {
    suspend operator fun invoke(
        deviceId: DeviceId,
        command: DeviceCommand
    ): Result<CommandExecutionResult> = runCatching {
        // 1. 获取设备信息
        val device = deviceRepository.getDevice(deviceId)
            ?: throw DeviceNotFoundException(deviceId)

        // 2. 获取当前用户
        val currentUser = userRepository.getCurrentUser()
            ?: throw UserNotAuthenticatedException()

        // 3. 授权检查
        val authResult = authorizationService.canUserControlDevice(
            user = currentUser,
            device = device,
            operation = command.toOperation()
        )

        if (authResult is AuthorizationResult.Failure) {
            throw UnauthorizedException(authResult.reason)
        }

        // 4. 验证命令参数
        validateCommand(device, command)

        // 5. 执行命令
        val result = commandExecutor.execute(device, command)

        // 6. 记录事件
        eventRepository.recordEvent(
            DeviceEvent(
                deviceId = device.id,
                userId = currentUser.id,
                type = EventType.COMMAND_EXECUTED,
                command = command,
                result = result,
                timestamp = System.currentTimeMillis()
            )
        )

        result
    }

    private fun validateCommand(device: Device, command: DeviceCommand) {
        // 验证设备是否支持该命令
        val capability = command.requiredCapability()
        if (!device.canExecute(capability)) {
            throw UnsupportedCommandException(
                "Device does not support $capability"
            )
        }

        // 验证命令参数范围
        command.validate()
    }
}

/**
 * 设备命令
 */
sealed class DeviceCommand {
    abstract fun requiredCapability(): Capability
    abstract fun validate()
    abstract fun toOperation(): DeviceOperation

    data class SetPower(val on: Boolean) : DeviceCommand() {
        override fun requiredCapability() = Capability.PowerControl(0)
        override fun validate() {}
        override fun toOperation() = DeviceOperation.POWER_CONTROL
    }

    data class SetBrightness(val level: Int) : DeviceCommand() {
        override fun requiredCapability() =
            Capability.BrightnessControl(0..100)

        override fun validate() {
            require(level in 0..100) {
                "Brightness level must be between 0 and 100"
            }
        }

        override fun toOperation() = DeviceOperation.BRIGHTNESS_CONTROL
    }

    data class StartRecording(val duration: Long) : DeviceCommand() {
        override fun requiredCapability() =
            Capability.MotionDetection(0.5f)

        override fun validate() {
            require(duration > 0) {
                "Recording duration must be positive"
            }
        }

        override fun toOperation() = DeviceOperation.RECORDING_CONTROL
    }
}

/**
 * 命令执行结果
 */
sealed class CommandExecutionResult {
    data class Success(val message: String) : CommandExecutionResult()
    data class Failure(val error: String, val code: Int) : CommandExecutionResult()
}

2.3 Repository接口定义

Repository接口定义在Domain层,实现在Data层。

// domain/repository/DeviceRepository.kt
package com.某品牌.domain.repository

/**
 * 设备仓储接口
 * 定义数据访问契约,不关心具体实现
 */
interface DeviceRepository {
    /**
     * 获取设备详情
     */
    suspend fun getDevice(id: DeviceId): Device?

    /**
     * 获取用户的所有设备
     */
    suspend fun getDevicesByUser(userId: UserId): List<Device>

    /**
     * 添加设备
     */
    suspend fun addDevice(device: Device): Result<Device>

    /**
     * 更新设备信息
     */
    suspend fun updateDevice(device: Device): Result<Unit>

    /**
     * 删除设备
     */
    suspend fun deleteDevice(id: DeviceId): Result<Unit>

    /**
     * 观察设备状态变化
     */
    fun observeDevice(id: DeviceId): Flow<Device>

    /**
     * 观察设备列表变化
     */
    fun observeDeviceList(userId: UserId): Flow<List<Device>>

    /**
     * 同步设备数据
     */
    suspend fun syncDevices(): Result<Unit>
}

三、数据流与依赖管理

3.1 数据流架构

Data Layer

Domain Layer

Presentation Layer

implements

ViewModel

View/Fragment

Use Case

Repository Interface

Entity

Repository Impl

Data Source

Mapper

3.2 依赖注入配置

使用Hilt进行依赖注入:

// di/DomainModule.kt
package com.某品牌.di

@Module
@InstallIn(SingletonComponent::class)
object DomainModule {

    @Provides
    @Singleton
    fun provideDeviceAuthorizationService(): DeviceAuthorizationService {
        return DeviceAuthorizationService()
    }

    @Provides
    fun provideGetDeviceListUseCase(
        deviceRepository: DeviceRepository,
        userRepository: UserRepository,
        authorizationService: DeviceAuthorizationService
    ): GetDeviceListUseCase {
        return GetDeviceListUseCase(
            deviceRepository,
            userRepository,
            authorizationService
        )
    }

    @Provides
    fun provideControlDeviceUseCase(
        deviceRepository: DeviceRepository,
        userRepository: UserRepository,
        authorizationService: DeviceAuthorizationService,
        commandExecutor: DeviceCommandExecutor,
        eventRepository: DeviceEventRepository
    ): ControlDeviceUseCase {
        return ControlDeviceUseCase(
            deviceRepository,
            userRepository,
            authorizationService,
            commandExecutor,
            eventRepository
        )
    }
}

3.3 Data层实现

// data/repository/DeviceRepositoryImpl.kt
package com.某品牌.data.repository

class DeviceRepositoryImpl(
    private val remoteDataSource: DeviceRemoteDataSource,
    private val localDataSource: DeviceLocalDataSource,
    private val deviceMapper: DeviceMapper
) : DeviceRepository {

    override suspend fun getDevice(id: DeviceId): Device? {
        // 先从本地获取
        val localDevice = localDataSource.getDevice(id.value)
        if (localDevice != null) {
            return deviceMapper.toDomain(localDevice)
        }

        // 本地没有则从远程获取
        return remoteDataSource.getDevice(id.value)
            .map { dto ->
                // 保存到本地
                val entity = deviceMapper.toEntity(dto)
                localDataSource.insertDevice(entity)
                deviceMapper.toDomain(entity)
            }
            .getOrNull()
    }

    override suspend fun getDevicesByUser(userId: UserId): List<Device> {
        return withContext(Dispatchers.IO) {
            // 并行获取本地和远程数据
            val localDevices = async {
                localDataSource.getDevicesByUser(userId.value)
            }

            val remoteDevices = async {
                remoteDataSource.getDevicesByUser(userId.value)
                    .getOrNull() ?: emptyList()
            }

            val local = localDevices.await()
            val remote = remoteDevices.await()

            // 合并并更新本地数据
            mergeAndUpdateLocal(local, remote)
        }
    }

    override fun observeDevice(id: DeviceId): Flow<Device> {
        return localDataSource.observeDevice(id.value)
            .map { deviceMapper.toDomain(it) }
    }

    override fun observeDeviceList(userId: UserId): Flow<List<Device>> {
        return localDataSource.observeDevicesByUser(userId.value)
            .map { entities ->
                entities.map { deviceMapper.toDomain(it) }
            }
    }

    override suspend fun syncDevices(): Result<Unit> = runCatching {
        val userId = getCurrentUserId()

        // 1. 获取远程最新数据
        val remoteDevices = remoteDataSource.getDevicesByUser(userId)
            .getOrThrow()

        // 2. 转换并保存到本地
        val entities = remoteDevices.map { deviceMapper.toEntity(it) }
        localDataSource.replaceAll(entities)

        // 3. 清理过期数据
        localDataSource.deleteOldDevices(
            threshold = System.currentTimeMillis() - 30.days()
        )
    }

    private suspend fun mergeAndUpdateLocal(
        local: List<DeviceEntity>,
        remote: List<DeviceDto>
    ): List<Device> {
        val localMap = local.associateBy { it.id }
        val remoteMap = remote.associateBy { it.id }

        // 合并逻辑:远程数据优先
        val mergedEntities = remoteMap.values.map { dto ->
            deviceMapper.toEntity(dto)
        }

        // 保存合并后的数据
        localDataSource.insertDevices(mergedEntities)

        return mergedEntities.map { deviceMapper.toDomain(it) }
    }
}

四、Presentation层实现

4.1 ViewModel实现

// presentation/device/DeviceListViewModel.kt
package com.某品牌.presentation.device

class DeviceListViewModel(
    private val getDeviceListUseCase: GetDeviceListUseCase,
    private val observeDeviceListUseCase: ObserveDeviceListUseCase,
    private val controlDeviceUseCase: ControlDeviceUseCase
) : ViewModel() {

    // UI状态
    private val _uiState = MutableStateFlow<DeviceListUiState>(
        DeviceListUiState.Loading
    )
    val uiState: StateFlow<DeviceListUiState> = _uiState.asStateFlow()

    // 当前过滤条件
    private val _filter = MutableStateFlow(DeviceFilter())

    init {
        observeDevices()
    }

    /**
     * 观察设备列表变化
     */
    private fun observeDevices() {
        viewModelScope.launch {
            combine(
                observeDeviceListUseCase(),
                _filter
            ) { devices, filter ->
                devices to filter
            }.collect { (devices, filter) ->
                handleDeviceListUpdate(devices, filter)
            }
        }
    }

    /**
     * 处理设备列表更新
     */
    private fun handleDeviceListUpdate(
        devices: List<Device>,
        filter: DeviceFilter
    ) {
        if (devices.isEmpty()) {
            _uiState.value = DeviceListUiState.Empty
            return
        }

        val deviceItems = devices.map { device ->
            DeviceUiModel(
                id = device.id.value,
                name = device.name,
                type = device.type.toDisplayString(),
                status = device.status.toDisplayStatus(),
                isControllable = device.isControllable(),
                capabilities = device.capabilities.map { it.toDisplayString() }
            )
        }

        _uiState.value = DeviceListUiState.Success(
            devices = deviceItems,
            filter = filter
        )
    }

    /**
     * 刷新设备列表
     */
    fun refreshDevices() {
        viewModelScope.launch {
            _uiState.value = DeviceListUiState.Loading

            getDeviceListUseCase(_filter.value)
                .onSuccess { devices ->
                    // 数据会通过Flow自动更新
                }
                .onFailure { error ->
                    _uiState.value = DeviceListUiState.Error(
                        message = error.message ?: "Unknown error"
                    )
                }
        }
    }

    /**
     * 更新过滤条件
     */
    fun updateFilter(filter: DeviceFilter) {
        _filter.value = filter
    }

    /**
     * 控制设备
     */
    fun controlDevice(deviceId: String, command: DeviceCommand) {
        viewModelScope.launch {
            controlDeviceUseCase(DeviceId(deviceId), command)
                .onSuccess { result ->
                    when (result) {
                        is CommandExecutionResult.Success -> {
                            // 显示成功提示
                            _uiState.update { currentState ->
                                if (currentState is DeviceListUiState.Success) {
                                    currentState.copy(
                                        message = "Command executed successfully"
                                    )
                                } else {
                                    currentState
                                }
                            }
                        }
                        is CommandExecutionResult.Failure -> {
                            _uiState.update { currentState ->
                                if (currentState is DeviceListUiState.Success) {
                                    currentState.copy(
                                        message = "Command failed: ${result.error}"
                                    )
                                } else {
                                    currentState
                                }
                            }
                        }
                    }
                }
                .onFailure { error ->
                    _uiState.update { currentState ->
                        if (currentState is DeviceListUiState.Success) {
                            currentState.copy(
                                message = "Error: ${error.message}"
                            )
                        } else {
                            currentState
                        }
                    }
                }
        }
    }
}

/**
 * UI状态
 */
sealed class DeviceListUiState {
    object Loading : DeviceListUiState()
    object Empty : DeviceListUiState()
    data class Success(
        val devices: List<DeviceUiModel>,
        val filter: DeviceFilter,
        val message: String? = null
    ) : DeviceListUiState()
    data class Error(val message: String) : DeviceListUiState()
}

/**
 * UI模型
 */
data class DeviceUiModel(
    val id: String,
    val name: String,
    val type: String,
    val status: DisplayStatus,
    val isControllable: Boolean,
    val capabilities: List<String>
)

data class DisplayStatus(
    val text: String,
    val color: Int
)

4.2 架构边界与通信

Data

Domain

Presentation

Activity/Fragment

ViewModel

UI Model

Use Case

Entity

Repository Interface

Repository Impl

Data Source

DTO/Entity

Mapper

依赖方向


五、测试策略

5.1 Domain层单元测试

Domain层完全独立于框架,测试简单直接。

// domain/usecase/GetDeviceListUseCaseTest.kt
class GetDeviceListUseCaseTest {

    private lateinit var deviceRepository: DeviceRepository
    private lateinit var userRepository: UserRepository
    private lateinit var authorizationService: DeviceAuthorizationService
    private lateinit var useCase: GetDeviceListUseCase

    @Before
    fun setup() {
        deviceRepository = mockk()
        userRepository = mockk()
        authorizationService = mockk()

        useCase = GetDeviceListUseCase(
            deviceRepository,
            userRepository,
            authorizationService
        )
    }

    @Test
    fun `invoke should return filtered and sorted devices`() = runTest {
        // Given
        val userId = UserId("user123")
        val user = createTestUser(userId)

        val devices = listOf(
            createTestDevice(
                id = "1",
                name = "Camera 1",
                type = DeviceType.CAMERA,
                status = DeviceStatus.ONLINE
            ),
            createTestDevice(
                id = "2",
                name = "Doorbell 1",
                type = DeviceType.DOORBELL,
                status = DeviceStatus.OFFLINE
            ),
            createTestDevice(
                id = "3",
                name = "Camera 2",
                type = DeviceType.CAMERA,
                status = DeviceStatus.ONLINE
            )
        )

        coEvery { userRepository.getCurrentUser() } returns user
        coEvery { deviceRepository.getDevicesByUser(userId) } returns devices

        val filter = DeviceFilter(
            types = setOf(DeviceType.CAMERA)
        )

        // When
        val result = useCase(filter)

        // Then
        assertTrue(result.isSuccess)
        val filteredDevices = result.getOrThrow()

        assertEquals(2, filteredDevices.size)
        assertTrue(filteredDevices.all { it.type == DeviceType.CAMERA })

        // 验证排序:在线设备在前
        assertEquals(DeviceStatus.ONLINE, filteredDevices[0].status)
        assertEquals(DeviceStatus.ONLINE, filteredDevices[1].status)
    }

    @Test
    fun `invoke should fail when user is not logged in`() = runTest {
        // Given
        coEvery { userRepository.getCurrentUser() } returns null

        // When
        val result = useCase(DeviceFilter())

        // Then
        assertTrue(result.isFailure)
        assertTrue(result.exceptionOrNull() is IllegalStateException)
    }
}

5.2 Use Case集成测试

// domain/usecase/ControlDeviceUseCaseTest.kt
class ControlDeviceUseCaseTest {

    private lateinit var deviceRepository: DeviceRepository
    private lateinit var userRepository: UserRepository
    private lateinit var authorizationService: DeviceAuthorizationService
    private lateinit var commandExecutor: DeviceCommandExecutor
    private lateinit var eventRepository: DeviceEventRepository
    private lateinit var useCase: ControlDeviceUseCase

    @Before
    fun setup() {
        deviceRepository = mockk()
        userRepository = mockk()
        authorizationService = mockk(relaxed = true)
        commandExecutor = mockk()
        eventRepository = mockk(relaxed = true)

        useCase = ControlDeviceUseCase(
            deviceRepository,
            userRepository,
            authorizationService,
            commandExecutor,
            eventRepository
        )
    }

    @Test
    fun `should execute command successfully when authorized`() = runTest {
        // Given
        val deviceId = DeviceId("device123")
        val device = createControllableDevice(deviceId)
        val user = createTestUser()
        val command = DeviceCommand.SetPower(true)

        coEvery { deviceRepository.getDevice(deviceId) } returns device
        coEvery { userRepository.getCurrentUser() } returns user
        every {
            authorizationService.canUserControlDevice(user, device, any())
        } returns AuthorizationResult.Success

        coEvery {
            commandExecutor.execute(device, command)
        } returns CommandExecutionResult.Success("Power turned on")

        // When
        val result = useCase(deviceId, command)

        // Then
        assertTrue(result.isSuccess)
        val executionResult = result.getOrThrow()
        assertTrue(executionResult is CommandExecutionResult.Success)

        // 验证事件记录
        coVerify {
            eventRepository.recordEvent(
                match {
                    it.deviceId == deviceId &&
                    it.userId == user.id &&
                    it.type == EventType.COMMAND_EXECUTED
                }
            )
        }
    }

    @Test
    fun `should fail when user is not authorized`() = runTest {
        // Given
        val deviceId = DeviceId("device123")
        val device = createControllableDevice(deviceId)
        val user = createTestUser()
        val command = DeviceCommand.SetPower(true)

        coEvery { deviceRepository.getDevice(deviceId) } returns device
        coEvery { userRepository.getCurrentUser() } returns user
        every {
            authorizationService.canUserControlDevice(user, device, any())
        } returns AuthorizationResult.Failure("User not authorized")

        // When
        val result = useCase(deviceId, command)

        // Then
        assertTrue(result.isFailure)
        assertTrue(result.exceptionOrNull() is UnauthorizedException)

        // 验证命令未执行
        coVerify(exactly = 0) {
            commandExecutor.execute(any(), any())
        }
    }
}

六、架构演进经验

6.1 迁移策略

从传统架构迁移到Clean Architecture的步骤:

传统架构

提取Repository接口

创建Domain层

实现Use Cases

重构ViewModel

完整Clean Architecture

阶段1:
接口抽象

阶段2:
领域建模

阶段3:
业务编排

阶段4:
视图解耦

迁移实践

// 第一步:提取Repository接口
// 从原有的DeviceManager中提取接口
interface DeviceRepository {
    suspend fun getDevices(): List<Device>
}

// 第二步:创建领域模型
// 将原有的DTO转换为领域实体
data class Device(
    val id: DeviceId,
    val name: String,
    val status: DeviceStatus
) {
    fun isAvailable() = status == DeviceStatus.ONLINE
}

// 第三步:实现Use Case
// 封装业务逻辑
class GetAvailableDevicesUseCase(
    private val repository: DeviceRepository
) {
    suspend operator fun invoke(): List<Device> {
        return repository.getDevices()
            .filter { it.isAvailable() }
    }
}

// 第四步:重构ViewModel
// 依赖Use Case而不是直接依赖Repository
class DeviceViewModel(
    private val getAvailableDevicesUseCase: GetAvailableDevicesUseCase
) : ViewModel() {
    fun loadDevices() {
        viewModelScope.launch {
            val devices = getAvailableDevicesUseCase()
            // Update UI state
        }
    }
}

6.2 常见陷阱

陷阱1:过度设计

// 错误:为简单CRUD创建Use Case
class GetUserByIdUseCase(
    private val repository: UserRepository
) {
    suspend operator fun invoke(id: String) = repository.getUserById(id)
}

// 正确:简单查询可以直接使用Repository
class UserViewModel(
    private val userRepository: UserRepository
) {
    fun loadUser(id: String) {
        viewModelScope.launch {
            val user = userRepository.getUserById(id)
            // ...
        }
    }
}

陷阱2:依赖方向错误

// 错误:Domain层依赖Data层
// domain/model/Device.kt
data class Device(
    val entity: DeviceEntity  // 依赖了Data层的Entity
)

// 正确:Data层依赖Domain层
// data/mapper/DeviceMapper.kt
class DeviceMapper {
    fun toDomain(entity: DeviceEntity): Device {
        return Device(
            id = DeviceId(entity.id),
            name = entity.name
        )
    }
}

6.3 性能优化

优化1:Use Case结果缓存

class GetDeviceListUseCase(
    private val deviceRepository: DeviceRepository
) {
    private var cachedResult: List<Device>? = null
    private var lastFetchTime: Long = 0

    suspend operator fun invoke(
        filter: DeviceFilter,
        forceRefresh: Boolean = false
    ): Result<List<Device>> {
        // 缓存5分钟有效
        val isCacheValid = System.currentTimeMillis() - lastFetchTime < 5.minutes()

        if (!forceRefresh && isCacheValid && cachedResult != null) {
            return Result.success(cachedResult!!.filter { applyFilter(it, filter) })
        }

        return runCatching {
            val devices = deviceRepository.getDevicesByUser(getCurrentUserId())
            cachedResult = devices
            lastFetchTime = System.currentTimeMillis()
            devices.filter { applyFilter(it, filter) }
        }
    }
}

七、总结

7.1 Clean Architecture的优势

  1. 可测试性:业务逻辑独立,易于单元测试
  2. 可维护性:关注点分离,修改影响范围小
  3. 独立性:核心业务不依赖框架和工具
  4. 灵活性:易于替换外部组件

7.2 实施建议

  1. 渐进式迁移:从新功能开始应用Clean Architecture
  2. 团队共识:确保团队理解架构原则
  3. 合理权衡:避免过度设计,保持务实
  4. 持续优化:根据实际情况调整架构

7.3 适用场景

Clean Architecture适合:

  • 大型复杂项目
  • 长期维护的项目
  • 业务逻辑复杂的应用
  • 需要高测试覆盖率的项目

不太适合:

  • 小型简单应用
  • 快速原型验证
  • 业务逻辑简单的工具类应用

参考资源

  1. Robert C. Martin - Clean Architecture
  2. Android Architecture Components
  3. Kotlin Coroutines Best Practices
  4. Domain-Driven Design
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐