使用ViewModel与Clean Code时,UI展示用的数据格式化逻辑应放在何处?
方案选择结论
优先选择方案1(格式化逻辑放在View层实现,保持ViewModel无Context依赖),无论从架构合理性还是测试效率角度都比方案2更优,具体分析如下:
方案2的核心问题
- 违背ViewModel的设计原则:ViewModel的核心设计目标是与Android Framework层解耦,即便注入不会内存泄漏的Application Context,也会让ViewModel依赖Android平台组件,无法运行纯JVM单元测试。插桩测试的执行速度比单元测试慢10倍以上,长期会大幅降低测试迭代效率。
- 不符合关注点分离:格式化访问drawable、字符串等操作属于UI渲染范畴的逻辑,本身就不该由不感知UI细节的ViewModel负责。
方案1的优化实现(避免View层代码臃肿)
不需要把所有格式化逻辑都堆在Activity/Fragment中,可以通过分层拆分保持代码整洁:
- 抽离独立的UI格式化工具类,统一存放依赖Context的资源转换逻辑,View层只需要传入ViewModel提供的原始数据即可拿到格式化结果,不会产生冗余代码。
- 对于需要跨页面复用的格式化逻辑,可以封装为公共UI层的扩展方法,所有View层组件都可以直接调用。
- 可引入
TextResource中间层做折中:如果需要ViewModel明确指定使用的资源ID,可参考以下封装实现,让ViewModel不需要依赖Context就能传递资源信息:
sealed class TextResource { // 直接返回原始字符串 data class RawText(val content: String) : TextResource() // 返回字符串资源ID + 占位参数 data class ResText(@StringRes val resId: Int, val args: List<Any> = emptyList()) : TextResource() }
View层只需要实现一个简单的转换方法,就能把TextResource转为实际渲染的字符串:
fun TextResource.asString(context: Context): String { return when(this) { is RawText -> content is ResText -> context.getString(resId, *args.toTypedArray()) } }
这种实现下ViewModel的所有逻辑都可以做纯JVM单元测试,不需要依赖Android环境,同时也避免了格式化逻辑散落在View层各处的问题。
内容的提问来源于stack exchange,提问作者Carlos Jiménez
相关产品推荐
相关产品推荐

