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

如何拆分大型复杂ViewModel或基于小型ViewModel构建主ViewModel?

拆分复杂ViewModel的实践方案

你的ViewModel目前承担了数据加载、过滤逻辑、状态管理等多个职责,随着功能迭代会越来越难维护。下面是几种拆分思路和重构示例:

一、按职责拆分独立子ViewModel

把不同业务模块的逻辑拆成单独的ViewModel,主ViewModel通过组合这些子ViewModel来协调工作,每个子ViewModel只负责单一职责:

  • 库存数据子ViewModel:负责库存数据的加载、过滤逻辑
  • 用户标签子ViewModel:负责用户标签的加载、选中状态管理
  • 提交状态子ViewModel:负责提交按钮的可用性判断

二、重构后的代码示例

1. 库存数据子ViewModel

class InventoryViewModel : ViewModel() {
    private val tag = "MyApp${this.javaClass.simpleName}"

    private val allItems = mutableStateListOf<InventoryItemDto?>()
    private val _filterByName = MutableLiveData<String>("")
    val filterByName: LiveData<String> get() = _filterByName
    private val _filterByAssignedToMe = MutableLiveData<Boolean>(false)
    val filterByAssignedToMe: LiveData<Boolean> get() = _filterByAssignedToMe

    private val _displayedItems = MutableLiveData<List<InventoryItemDto?>>()
    val displayedItems: LiveData<List<InventoryItemDto?>> get() = _displayedItems
    private val _error = MutableLiveData<String>("")
    val error: LiveData<String> get() = _error

    init {
        loadInventoryItems()
        combineFilters()
    }

    fun setFilterByName(text: String) {
        _filterByName.value = text
    }

    fun setFilterByAssignedToMe(isChecked: Boolean) {
        _filterByAssignedToMe.value = isChecked
    }

    private fun combineFilters() {
        viewModelScope.launch {
            combine(
                filterByName.asFlow(),
                filterByAssignedToMe.asFlow()
            ) { nameFilter, assignedFilter ->
                applyFilters(nameFilter, assignedFilter)
            }.collect()
        }
    }

    private fun applyFilters(nameFilter: String, assignedFilter: Boolean) {
        Log.d(tag, "应用筛选:名称过滤=$nameFilter")
        val filtered = allItems.filter { item ->
            val matchesName = item?.name?.lowercase()?.contains(nameFilter.lowercase()) ?: false
            val matchesAssigned = if (assignedFilter) {
                // 补充"分配给我"的判断逻辑
                true
            } else true
            matchesName && matchesAssigned
        }
        _displayedItems.value = filtered
    }

    private fun loadInventoryItems() {
        Log.d(tag, "加载库存数据...")
        viewModelScope.launch {
            try {
                val apiResponse = ApiResponse(ApiAdapter.apiClient.findAllInventoryItems())
                if (apiResponse.code == 200 && apiResponse.body != null) {
                    allItems.clear()
                    allItems.addAll(apiResponse.body)
                    applyFilters(_filterByName.value.orEmpty(), _filterByAssignedToMe.value ?: false)
                    Log.d(tag, "库存数据加载完成")
                } else {
                    _error.value = apiResponse.code.toString()
                    Log.d(tag, "库存数据加载失败")
                }
            } catch (t: Throwable) {
                _error.value = t.message.toString()
            }
        }
    }
}

2. 用户标签子ViewModel

class UserTagViewModel : ViewModel() {
    private val tag = "MyApp${this.javaClass.simpleName}"

    private val _userTags = MutableLiveData<List<UserTag>>()
    val userTags: LiveData<List<UserTag>> get() = _userTags
    private val _selectedUserTag = MutableLiveData<UserTag?>()
    val selectedUserTag: LiveData<UserTag?> get() = _selectedUserTag

    init {
        loadUserTags()
    }

    fun setSelectedUserTag(tag: UserTag?) {
        _selectedUserTag.value = tag
    }

    private fun loadUserTags() {
        // 替换为真实接口请求
        val temp = mutableListOf<UserTag>()
        repeat(50) {
            val epc = getRandomHexString(24).uppercase()
            temp.add(UserTag(epc, 0, "0"))
        }
        viewModelScope.launch {
            delay(100)
            _userTags.value = temp
            Log.d(tag, "用户标签加载完成")
        }
    }

    private fun getRandomHexString(numchars: Int): String {
        val r = Random()
        val sb = StringBuffer()
        while (sb.length < numchars) {
            sb.append(Integer.toHexString(r.nextInt()))
        }
        return sb.toString().substring(0, numchars)
    }
}

3. 提交状态子ViewModel

class SubmitStateViewModel(
    private val inventoryViewModel: InventoryViewModel,
    private val userTagViewModel: UserTagViewModel
) : ViewModel() {
    private val _canSubmit = MutableLiveData<Boolean>(false)
    val canSubmit: LiveData<Boolean> get() = _canSubmit

    init {
        observeSelectionChanges()
    }

    private fun observeSelectionChanges() {
        viewModelScope.launch {
            combine(
                inventoryViewModel.displayedItems.asFlow(),
                userTagViewModel.selectedUserTag.asFlow()
            ) { items, selectedTag ->
                // 判断是否可以提交:至少有一个选中的库存项和用户标签
                val hasSelectedItem = items.any { /* 补充库存项选中判断逻辑 */ true }
                hasSelectedItem && selectedTag != null
            }.collect { canSubmit ->
                _canSubmit.value = canSubmit
            }
        }
    }
}

4. 主ViewModel(组合子ViewModel)

class AssignUserTagToInventoryItemViewModel : ViewModel() {
    private val tag = "MyApp${this.javaClass.simpleName}"

    val inventoryViewModel = InventoryViewModel()
    val userTagViewModel = UserTagViewModel()
    val submitStateViewModel = SubmitStateViewModel(inventoryViewModel, userTagViewModel)

    init {
        Log.d(tag, "主ViewModel初始化完成")
    }
}

三、额外优化建议

  • 使用mutableStateListOf替代ArrayList,配合Flow的combine操作符可以更优雅地处理多条件筛选,避免手动调用applyFilters
  • 每个子ViewModel只暴露必要的LiveData和方法,减少职责交叉
  • 主ViewModel只负责协调子ViewModel,不处理具体业务逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 10:02:23