Skip to content

Android 架构核心原则:单一数据源(SSOT)与单向数据流(UDF)实战指南

在Android开发中,随着应用复杂度提升,数据混乱、状态不一致、调试困难等问题频发,而单一数据源(Single Source of Truth, SSOT)与单向数据流(Unidirectional Data Flow, UDF)正是解决这些痛点的核心架构原则。

两者协同工作,能够让应用架构更清晰、可维护性更强、可测试性更高,是现代Android架构(如MVVM、MVI)的基石。

本指南将从概念解析、核心价值、实战落地、常见问题四个维度,帮助开发者彻底掌握SSOT与UDF的应用,结合Jetpack组件打造健壮的Android应用。

一、核心概念解析:SSOT与UDF是什么?

1.1 单一数据源(SSOT:Single Source of Truth)

单一数据源是指在应用架构中,对于任意一类数据,始终存在且仅存在一个“权威来源”,所有组件对该数据的读取、修改都必须通过这个来源,禁止组件间直接共享或修改数据副本。

核心意义:解决Android开发中“数据多副本不一致”的痛点——例如本地数据库、网络缓存、内存缓存多份数据同步混乱,导致UI展示异常、逻辑判断错误,同时降低数据维护成本,简化调试流程(只需跟踪单一数据源的变化)。

核心要求:

  • 唯一性:同一类数据(如用户信息、商品列表)仅存在一个权威数据源,避免多源头修改。

  • 统一性:所有组件(UI、ViewModel、UseCase等)均从该数据源读取数据,修改数据需通过统一入口(如Repository的方法),禁止直接操作数据源底层(如Room Dao、Retrofit接口)。

  • 一致性:数据源自身保证数据的完整性和一致性(如本地与远程数据同步时,通过Repository统一处理合并逻辑)。

1.2 单向数据流(UDF:Unidirectional Data Flow)

单向数据流是指应用中所有数据的流动方向固定不变,形成“数据输入→处理→输出→展示”的闭环,禁止反向流动(如UI直接修改数据源,或数据从UI层反向传递到数据层)。

核心意义:解决“数据流混乱、状态不可追溯”的问题——例如UI点击事件直接修改数据库,导致无法跟踪状态变化原因,而UDF通过固定流向,让每一次状态变化都可预测、可调试、可复现。

标准流向(以MVVM架构为例):

  1. 用户交互/外部事件(输入):UI层触发事件(如点击刷新按钮、下拉加载),或外部数据推送(如推送通知)。

  2. 事件处理:ViewModel接收事件,调用领域层(UseCase)或数据层(Repository)的方法,处理业务逻辑。

  3. 数据获取/修改:Repository从单一数据源(本地/远程)获取或修改数据,返回处理结果。

  4. 状态分发(输出):ViewModel将处理后的结果转换为UI可展示的状态(如加载中、成功、失败),通过可观察对象(Flow/LiveData)分发给UI层。

  5. UI渲染(展示):UI层观察ViewModel的状态,根据状态更新界面,不做任何业务逻辑处理,仅负责展示。

核心要求:数据流不可逆、状态不可变(UI层仅读取状态,不修改状态;ViewModel仅分发状态,不直接接收UI层的状态修改请求,需通过事件触发)。

1.3 SSOT与UDF的关联:相辅相成,缺一不可

SSOT是UDF的基础:没有单一数据源,UDF的“数据输出”会出现多源头、不一致的问题,导致UI展示的状态混乱;UDF是SSOT的保障:没有单向数据流,组件可能直接修改数据源,破坏SSOT的唯一性和一致性。两者结合,才能实现“数据可追溯、状态可预测、架构可维护”的目标。

二、实战准备:技术栈选型与架构基础

本实战将基于现代Android主流技术栈,结合MVVM架构,落地SSOT与UDF原则,技术栈选型如下(均为Jetpack核心组件,降低学习成本,适配绝大多数Android项目):

  • 语言:Kotlin(空安全、协程、Flow,适配UDF的数据流处理)。

  • 架构模式:MVVM(Model-View-ViewModel),清晰区分UI层、ViewModel层、数据层。

  • 数据层:Room(本地数据源,SQLite封装)、Retrofit(远程数据源,网络请求)、Repository(单一数据源入口)。

  • ViewModel层:ViewModel(持有UI状态,处理事件)、Flow(单向数据流分发,替代LiveData实现更灵活的状态管理)。

  • UI层:Compose(声明式UI,更适配UDF的状态驱动渲染;若使用XML布局,逻辑一致,仅渲染方式不同)。

  • 其他:Coroutines(处理异步任务,如网络请求、数据库操作)、Dagger Hilt(依赖注入,简化组件间依赖,提升可测试性)。

架构分层约定(严格遵循单一职责,支撑SSOT与UDF):

  • UI层:仅负责展示UI、接收用户交互,观察ViewModel的状态,不包含任何业务逻辑和数据处理。

  • ViewModel层:接收UI层的事件,调用UseCase/Repository处理逻辑,将结果转换为UI状态,通过Flow分发,不直接操作数据源。

  • 领域层(可选,中小型项目可省略,整合到Repository):封装核心业务逻辑(如数据过滤、转换),解耦ViewModel与Repository。

  • 数据层:包含Repository、本地数据源(Room)、远程数据源(Retrofit),是SSOT的核心载体,负责数据的获取、存储、同步。

三、核心实战:SSOT+UDF 完整落地案例

本案例将实现一个“用户列表”功能,涵盖“下拉刷新获取最新用户、点击item查看用户详情”两个核心交互,完整落地SSOT与UDF原则,重点展示:单一数据源的封装(Repository)、单向数据流的流转(UI→ViewModel→Repository→ViewModel→UI)、状态驱动的UI渲染。

3.1 需求梳理(明确输入与输出)

  • 输入事件:下拉刷新(请求远程最新用户数据)、点击用户item(触发查看详情事件)。

  • 数据处理:刷新时,先请求远程数据,成功后更新本地数据库(单一数据源),失败则展示本地缓存数据;点击item时,传递用户ID,获取对应用户详情(从单一数据源读取)。

  • 输出状态:加载中(刷新时)、用户列表展示(成功)、加载失败(提示错误)、用户详情展示(成功)。

3.2 第一步:封装单一数据源(SSOT核心:Repository)

Repository是单一数据源的唯一入口,负责整合本地(Room)和远程(Retrofit)数据源,统一处理数据同步逻辑,对外提供统一的API,保证所有组件仅通过Repository获取/修改数据。

3.2.1 定义数据模型(Entity与DTO)

区分本地数据库实体(Entity)和远程接口返回数据(DTO),避免数据耦合,通过映射转换统一数据格式。

kotlin
// 1. 远程DTO(Retrofit接口返回格式)
data class UserDto(
    @SerializedName("id") val id: Int,
    @SerializedName("name") val name: String,
    @SerializedName("avatar") val avatarUrl: String
)

// 2. 本地Entity(Room数据库实体,单一数据源的存储载体)
@Entity(tableName = "user")
data class UserEntity(
    @PrimaryKey val id: Int,
    val name: String,
    val avatarUrl: String,
    @ColumnInfo(defaultValue = "0") val updateTime: Long // 用于数据同步判断
)

// 3. UI展示模型(ViewModel分发给UI层的模型,与数据源解耦)
data class UserUiModel(
    val id: Int,
    val name: String,
    val avatarUrl: String
)

// 映射扩展函数(转换DTO→Entity、Entity→UiModel)
fun UserDto.toEntity(): UserEntity = UserEntity(
    id = id,
    name = name,
    avatarUrl = avatarUrl,
    updateTime = System.currentTimeMillis()
)

fun UserEntity.toUiModel(): UserUiModel = UserUiModel(
    id = id,
    name = name,
    avatarUrl = avatarUrl
)

3.2.2 实现本地与远程数据源

kotlin
// 1. 远程数据源(Retrofit接口)
interface UserRemoteDataSource {
    @GET("/users")
    suspend fun getRemoteUsers(): List<UserDto>
    
    @GET("/users/{id}")
    suspend fun getRemoteUserDetail(@Path("id") id: Int): UserDto
}

// 2. 本地数据源(Room Dao)
@Dao
interface UserLocalDataSource {
    @Query("SELECT * FROM user ORDER BY updateTime DESC")
    fun getLocalUsers(): Flow<List<UserEntity>> // 用Flow观察本地数据变化
    
    @Query("SELECT * FROM user WHERE id = :id")
    suspend fun getLocalUserDetail(id: Int): UserEntity?
    
    @Insert(onConflict = OnConflictStrategy.REPLACE) // 冲突时替换,保证数据唯一
    suspend fun insertUsers(users: List<UserEntity>)
    
    @Delete
    suspend fun deleteAllUsers()
}

3.2.3 实现Repository(单一数据源入口)

Repository整合本地和远程数据源,对外提供统一方法,处理数据同步逻辑(如刷新时先远程后本地),保证所有组件仅通过Repository操作数据,实现SSOT。

kotlin
// 注入本地和远程数据源(依赖注入,简化依赖管理)
class UserRepository @Inject constructor(
    private val remoteDataSource: UserRemoteDataSource,
    private val localDataSource: UserLocalDataSource
) {
    // 1. 获取用户列表:单一数据源入口(优先远程,失败用本地,成功更新本地)
    suspend fun getUsers(refresh: Boolean = false): Result<List<UserEntity>> {
        return try {
            if (refresh) {
                // 下拉刷新:请求远程数据,更新本地数据库
                val remoteUsers = remoteDataSource.getRemoteUsers()
                val userEntities = remoteUsers.map { it.toEntity() }
                localDataSource.insertUsers(userEntities)
            }
            // 无论是否刷新,最终从本地数据源读取(单一数据源的核心:所有读取走本地)
            val localUsers = localDataSource.getLocalUsers().first() // Flow取第一个值
            Result.success(localUsers)
        } catch (e: Exception) {
            // 远程请求失败,返回本地缓存(若本地无数据,返回失败)
            val localUsers = localDataSource.getLocalUsers().firstOrNull()
            localUsers?.let { Result.success(it) } ?: Result.failure(e)
        }
    }
    
    // 2. 获取用户详情:从本地数据源读取(单一数据源)
    suspend fun getUserDetail(id: Int): Result<UserEntity?> {
        return try {
            val user = localDataSource.getLocalUserDetail(id)
            Result.success(user)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
    
    // 3. 观察本地用户列表变化(用于UI实时刷新,如数据库更新后自动同步)
    fun observeUsers(): Flow<List<UserEntity>> = localDataSource.getLocalUsers()
}

// 注意:Repository仅暴露数据操作方法,不处理业务逻辑和UI状态转换,保持单一职责

3.3 第二步:实现单向数据流(UDF核心:ViewModel+UI)

遵循UDF的标准流向,实现“UI事件→ViewModel处理→Repository获取数据→ViewModel转换状态→UI渲染”的闭环,保证数据流单向、状态可预测。

3.3.1 ViewModel:事件处理与状态分发

ViewModel的核心职责:接收UI层的事件,调用Repository的方法处理数据,将数据结果转换为UI可展示的状态,通过Flow分发给UI层,不持有UI引用,不直接操作数据源。

kotlin
// 1. 定义UI状态(不可变,仅用于展示,UI层不可修改)
sealed class UserUiState {
    object Loading : UserUiState() // 加载中
    data class Success(val users: List<UserUiModel>) : UserUiState() // 成功(用户列表)
    data class DetailSuccess(val user: UserUiModel?) : UserUiState() // 成功(用户详情)
    data class Error(val message: String) : UserUiState() // 失败
}

// 2. 定义UI事件(输入,UI层触发,ViewModel接收)
sealed class UserUiEvent {
    object RefreshUsers : UserUiEvent() // 下拉刷新
    data class ClickUserItem(val userId: Int) : UserUiEvent() // 点击用户item
}

// 3. ViewModel实现(处理事件,分发状态)
class UserViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {
    // 私有事件流(接收UI层事件,用MutableSharedFlow,支持单次发送)
    private val _uiEvent = MutableSharedFlow<UserUiEvent>()
    // 公开状态流(分发UI状态,用StateFlow,保证状态可观察、不可变)
    private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()

    init {
        // 监听UI事件,处理逻辑(数据流入口)
        viewModelScope.launch {
            _uiEvent.collect { event ->
                when (event) {
                    is UserUiEvent.RefreshUsers -> handleRefreshUsers()
                    is UserUiEvent.ClickUserItem -> handleClickUserItem(event.userId)
                }
            }
        }
        // 初始化:加载本地缓存数据
        viewModelScope.launch {
            handleRefreshUsers(refresh = false)
        }
    }

    // 处理下拉刷新事件
    private suspend fun handleRefreshUsers(refresh: Boolean = true) {
        _uiState.value = UserUiState.Loading // 发送加载中状态
        val result = userRepository.getUsers(refresh)
        _uiState.value = when (result) {
            is Result.Success -> {
                val userUiModels = result.data.map { it.toUiModel() }
                UserUiState.Success(userUiModels) // 发送成功状态(用户列表)
            }
            is Result.Failure -> UserUiState.Error(result.exception.message ?: "加载失败") // 发送失败状态
        }
    }

    // 处理点击用户item事件
    private suspend fun handleClickUserItem(userId: Int) {
        _uiState.value = UserUiState.Loading // 发送加载中状态
        val result = userRepository.getUserDetail(userId)
        _uiState.value = when (result) {
            is Result.Success -> {
                val userUiModel = result.data?.toUiModel()
                UserUiState.DetailSuccess(userUiModel) // 发送成功状态(用户详情)
            }
            is Result.Failure -> UserUiState.Error(result.exception.message ?: "获取详情失败") // 发送失败状态
        }
    }

    // 对外提供方法:UI层触发事件(数据流入口,仅允许UI层发送事件,不允许直接修改状态)
    fun sendEvent(event: UserUiEvent) {
        viewModelScope.launch {
            _uiEvent.emit(event)
        }
    }
}

// 关键说明:
// 1. 状态(uiState)用StateFlow,不可变(对外暴露asStateFlow),UI层仅能观察,不能修改;
// 2. 事件(uiEvent)用MutableSharedFlow,UI层通过sendEvent发送事件,ViewModel统一处理;
// 3. 所有数据处理均调用Repository的方法,不直接操作本地/远程数据源,遵循SSOT;
// 4. 数据流不可逆:UI→ViewModel(事件),ViewModel→UI(状态),无反向流动。

3.3.2 UI层:事件触发与状态渲染(Compose实现)

UI层的核心职责:展示状态、触发事件,不包含任何业务逻辑和数据处理,严格遵循“状态驱动渲染”,仅观察ViewModel的uiState,根据状态更新界面。

kotlin
// UserListScreen:用户列表界面(UI层)
@Composable
fun UserListScreen(
    viewModel: UserViewModel = hiltViewModel() // 注入ViewModel
) {
    // 观察ViewModel的状态(数据流出口)
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    // 下拉刷新组件(触发刷新事件)
    val pullRefreshState = rememberPullRefreshState(
        refreshing = uiState is UserUiState.Loading,
        onRefresh = { viewModel.sendEvent(UserUiEvent.RefreshUsers) }
    )

    Box(modifier = Modifier.fillMaxSize()) {
        PullRefresh(
            state = pullRefreshState,
            modifier = Modifier.fillMaxSize()
        ) {
            // 根据状态渲染UI
            when (val state = uiState) {
                is UserUiState.Loading -> {
                    // 加载中:展示进度条
                    CircularProgressIndicator(
                        modifier = Modifier.align(Alignment.Center)
                    )
                }
                is UserUiState.Success -> {
                    // 成功:展示用户列表
                    LazyColumn(modifier = Modifier.fillMaxSize()) {
                        items(state.users) { user ->
                            UserItem(
                                user = user,
                                onClick = {
                                    // 点击事件:发送点击事件给ViewModel
                                    viewModel.sendEvent(UserUiEvent.ClickUserItem(user.id))
                                }
                            )
                        }
                    }
                }
                is UserUiState.DetailSuccess -> {
                    // 详情状态:跳转详情页(或弹窗展示)
                    state.user?.let { user ->
                        UserDetailDialog(
                            user = user,
                            onDismiss = {
                                // 关闭详情:重新加载列表状态
                                viewModel.sendEvent(UserUiEvent.RefreshUsers)
                            }
                        )
                    } ?: run {
                        Text(
                            text = "用户不存在",
                            modifier = Modifier.align(Alignment.Center)
                        )
                    }
                }
                is UserUiState.Error -> {
                    // 失败:展示错误提示,提供重试按钮
                    Column(
                        modifier = Modifier.align(Alignment.Center),
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {
                        Text(text = state.message)
                        Button(
                            onClick = { viewModel.sendEvent(UserUiEvent.RefreshUsers) },
                            modifier = Modifier.topPadding(16.dp)
                        ) {
                            Text(text = "重试")
                        }
                    }
                }
            }
        }
    }
}

// 辅助组件:用户item
@Composable
fun UserItem(user: UserUiModel, onClick: () -> Unit) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .clickable(onClick = onClick)
            .padding(16.dp),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically
    ) {
        Text(text = user.name)
        Image(
            painter = rememberAsyncImagePainter(user.avatarUrl),
            contentDescription = "用户头像",
            modifier = Modifier.size(40.dp)
        )
    }
}

// 辅助组件:用户详情弹窗
@Composable
fun UserDetailDialog(user: UserUiModel, onDismiss: () -> Unit) {
    AlertDialog(
        onDismissRequest = onDismiss,
        title = { Text(text = "用户详情") },
        text = {
            Column {
                Text(text = "ID:${user.id}")
                Text(text = "姓名:${user.name}")
            }
        },
        confirmButton = {
            Button(onClick = onDismiss) {
                Text(text = "确定")
            }
        }
    )
}

// 关键说明:
// 1. UI层仅通过collectAsStateWithLifecycle观察ViewModel的状态,根据状态渲染不同界面;
// 2. 所有用户交互(下拉刷新、点击item、重试)均通过sendEvent发送事件给ViewModel,不直接处理逻辑;
// 3. 不持有任何数据源引用,不修改任何数据,仅负责“展示”和“触发事件”,遵循UDF的单向流动。

3.4 第三步:依赖注入与运行验证(Hilt)

使用Dagger Hilt实现依赖注入,简化组件间依赖(如Repository注入ViewModel、本地/远程数据源注入Repository),避免硬编码,提升可测试性和可维护性。

kotlin
// 1. 配置Hilt(Application层)
@HiltAndroidApp
class MyApp : Application()

// 2. 提供远程数据源实例(Retrofit)
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Singleton
    @Provides
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/") // 替换为实际接口地址
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    @Singleton
    @Provides
    fun provideUserRemoteDataSource(retrofit: Retrofit): UserRemoteDataSource {
        return retrofit.create(UserRemoteDataSource::class.java)
    }
}

// 3. 提供本地数据源实例(Room)
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
    @Singleton
    @Provides
    fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "app_database"
        ).build()
    }

    @Provides
    fun provideUserLocalDataSource(database: AppDatabase): UserLocalDataSource {
        return database.userDao()
    }
}

// 4. Room数据库实例
@Database(entities = [UserEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserLocalDataSource
}

运行验证要点:

  • 下拉刷新:触发远程请求,成功后本地数据库更新,UI展示最新列表(单一数据源生效)。

  • 断网刷新:远程请求失败,UI展示本地缓存数据(单一数据源的容错性)。

  • 点击item:触发详情事件,ViewModel从Repository获取本地数据,展示详情(单向数据流生效)。

  • 调试验证:所有数据变化均通过Repository,所有状态变化均通过ViewModel分发,可通过日志跟踪数据流和状态变化。

四、常见问题与解决方案(实战避坑)

4.1 问题1:多组件修改数据源,破坏SSOT

现象:UI层或ViewModel直接调用Room Dao、Retrofit接口修改数据,导致数据多副本不一致。

解决方案:

  • 严格封装:仅Repository持有本地/远程数据源的引用,对外不暴露任何Dao、Retrofit接口。

  • 依赖注入:通过Hilt注入Repository,禁止组件自行创建本地/远程数据源实例。

  • 代码检查:通过lint规则禁止直接调用Dao、Retrofit接口(可选)。

4.2 问题2:数据流反向流动,破坏UDF

现象:UI层直接修改ViewModel的状态,或ViewModel直接接收UI层的状态修改请求,导致数据流混乱。

解决方案:

  • 状态不可变:ViewModel的uiState对外暴露为StateFlow(不可变),仅内部可修改。

  • 事件驱动:UI层仅能通过sendEvent发送事件,ViewModel通过处理事件修改状态,不直接接收状态修改请求。

  • 职责分离:UI层仅渲染状态,不做任何数据处理;ViewModel仅处理事件、分发状态,不持有UI引用。

4.3 问题3:本地与远程数据同步混乱,SSOT失效

现象:远程数据更新后,本地数据库未同步,或同步逻辑错误,导致读取的数据不一致。

解决方案:

  • 统一同步逻辑:所有数据同步(远程→本地)均在Repository中实现,如刷新时先请求远程,成功后插入本地(替换冲突数据)。

  • 时间戳判断:给本地数据添加updateTime字段,同步时根据时间戳判断数据新旧,避免无效同步。

  • 异常处理:远程请求失败时,优先使用本地缓存,同时给出错误提示,允许用户重试。

4.4 问题4:状态过多,UI渲染逻辑混乱

现象:ViewModel定义大量状态,UI层需要处理复杂的状态判断,导致代码冗余、易出错。

解决方案:

  • 密封类封装状态:使用sealed class统一管理所有UI状态(如本案例的UserUiState),避免状态分散。

  • 状态合并:将相关状态合并为一个复合状态(如加载中、成功、失败合并为列表状态),减少状态数量。

  • 辅助组件:将复杂的UI渲染逻辑抽取为独立组件(如本案例的UserItem、UserDetailDialog),简化主界面代码。

五、最佳实践总结(SSOT+UDF核心要点)

  • SSOT最佳实践:

    • 所有数据仅存在一个权威来源(优先本地数据库,远程数据用于同步更新本地)。

    • Repository是唯一的数据入口/出口,统一处理本地与远程数据的同步、读取、修改。

    • 禁止组件间直接共享数据副本,所有数据操作均通过Repository。

  • UDF最佳实践:

  • 严格遵循“输入→处理→输出→展示”的单向流向,数据流不可逆。

  • 状态不可变:UI层仅读取状态,ViewModel仅分发状态,修改状态需通过事件触发。

  • 用Flow/StateFlow管理数据流,替代LiveData实现更灵活的状态监听(如协程适配、背压处理)。

  • 架构协同:

  • SSOT与UDF必须结合使用,缺一不可,才能实现架构的健壮性和可维护性。

  • 分层清晰:UI层(展示)、ViewModel层(事件+状态)、数据层(SSOT),严格遵循单一职责。

  • 依赖注入:使用Hilt简化组件依赖,提升可测试性和可扩展性。

六、扩展建议(进阶方向)

  • 结合MVI架构:MVI是UDF的极致体现,将UI状态和事件进一步规范化,可尝试将本案例改造为MVI架构。

  • 状态持久化:使用SavedStateHandle保存ViewModel的状态,避免屏幕旋转时状态丢失。

  • 异常统一处理:封装全局异常处理机制,在Repository或ViewModel中统一捕获和处理异常,简化UI层的错误处理逻辑。

  • 测试实践:编写单元测试(测试Repository的数据同步逻辑、ViewModel的事件处理)和UI测试(测试状态渲染),验证SSOT与UDF的正确性。

结语:单一数据源(SSOT)与单向数据流(UDF)不是“银弹”,但却是解决Android架构混乱、数据不一致的核心原则。在实际项目中,无需过度复杂,只需严格遵循“单一数据源入口、数据流单向流动”的核心思想,结合Jetpack组件落地,就能显著提升应用的可维护性、可测试性,降低后期迭代成本。

随便写写的,喜欢就好。