You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MVVM/DDD/整洁架构:ViewModel是否应向View暴露领域模型?

ViewModel暴露领域模型是否合理?View数据预处理应放在何处?

尽管这个问题关联Android平台,但本质是独立于平台的架构基础问题。我学习了MVVM、DDD及整洁架构的相关概念,也研究了GitHub上的大量示例,但始终对一个问题存疑:ViewModel向View暴露领域模型的做法是否合理,以及View所需数据应在何处完成预处理。

比如我们有这样的领域模型:

data class News(
    val title: String,
    val content: String,
    val isFeatured: Boolean,
    val publishedAt: LocalDateTime,
    val tags: List<Tag>
)

实现新闻列表的ViewModel时,现有示例普遍是这么写的:

@HiltViewModel
class NewsListViewModel @Inject constructor(
    private val newsRepository: NewsRepository,
) : ViewModel() {
    
    // 也可以用UiState或MutableStateFlow包装
    private val _news: MutableLiveData<List<News>> = MutableLiveData()
    val news: LiveData<List<News>>

    fun loadData() {
        // 示例,实际可以用Flow、RxJava、CompletionBlock等实现
        val loadedNews: List<News> = newsRepository.getNews()
        news.postValue(loadedNews)
    }

    fun onNewsClicked(news: News) {
        // 处理点击逻辑...
    }
}

不管View是用Compose、带RecyclerView的Fragment哪种方式消费这些数据,最终都得自己处理领域模型来完成展示。所以我想问:遵循MVVM与整洁架构的规范,在ViewModel中定义View所需的全部数据与信息会不会更合理、更整洁?

我设想的写法如下:

首先定义适配View的数据类:

// 命名还不确定,如果不需要复用,可以作为ViewModel的嵌套类,比如NewsListViewModel.Item
data class NewsListItemViewModel(
    val title: String,
    val content: AnnotatedString,
    val featuredIcon: Int,
    val backgroundColorRes: Int,
    val publishedAt: String,
    val tags: List<String>,
    val onClick: () -> Unit
)

然后是对应的ViewModel实现:

@HiltViewModel
class NewsListViewModel @Inject constructor(
    private val newsRepository: NewsRepository,
) : ViewModel() {
    
    // 也可以用UiState或MutableStateFlow包装
    private val _news: MutableLiveData<List<NewsListItemViewModel>> = MutableLiveData()
    val news: LiveData<List<NewsListItemViewModel>>

    fun loadData() {
        // 示例
        val loadedNews: List<News> = newsRepository.getNews()
        val newsItems = loadedNews.map { news ->
            NewsListItemViewModel(
                title = news.title,
                content = Html.fromHtml(news.content),
                featuredIcon = if (news.isFeatured) R.drawable.ic_featured else 0,
                backgroundColorRes = if (news.isFeatured) R.color.bg_featured else R.color.bg_default,
                publishedAt = dateFormatter.format(news.publishedAt),
                tags = news.tags.map { it.title },
                onClick = { onNewsClicked(news) }
            )
        }
        news.postValue(newsItems)
    }

    fun onNewsClicked(news: News) {
        // 处理点击逻辑...
    }
}

内容的提问来源于stack exchange,提问作者Kevin Flachsmann

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 04:25:07