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

Android MVVM架构如何避免重复函数?Jetpack Compose场景

解决Jetpack Compose中增减数量逻辑的代码重复问题

问题背景

我用Jetpack Compose开发一款简易游戏,包含3个页面:

  • HeroesScreen:展示所有英雄,支持选择单个/多个相同角色
  • HeroDetailsScreen:展示英雄详情,可多次选择同一角色增加持有数量
  • ShoppingCartScreen:调整各角色数量(增减操作)

每个页面对应一套ViewModel+Repository:

HeroesScreen -> HeroesViewModel -> HeroesRepository
HeroDetailsScreen -> HeroDetailsViewModel -> HeroDetailsRepository
ShoppingCartScreen -> ShoppingCartViewModel -> ShoppingCartRepository

每个Repository包含8-12个业务API,但增减数量的两个函数在三个Repository和ViewModel中完全重复。我考虑过把这两个函数放到单个Repository中,再注入到其他ViewModel,但担心ShoppingCartRepository和HeroDetailsViewModel没有直接关联,这种方式是否合理?

补充代码示例(重复部分)

ViewModel重复逻辑(以ShoppingCartViewModel为例):

class ShoppingCartViewModel @Inject constructor(
    private val repo: ShoppingCartRepository
): ViewModel() {
    var incrementQuantityResult by mutableStateOf<Result<Boolean>>(Result.Success(false))
        private set
    var decrementQuantityResult by mutableStateOf<Result<Boolean>>(Result.Success(false))
        private set

    fun incrementQuantity(heroId: String) = viewModelScope.launch {
        repo.incrementQuantity(heroId).collect { result ->
            incrementQuantityResult = result
        }
    }

    fun decrementQuantity(heroId: String) = viewModelScope.launch {
        repo.decrementQuantity(heroId).collect { result ->
            decrementQuantityResult = result
        }
    }
}

Repository重复逻辑(以ShoppingCartRepositoryImpl为例):

class ShoppingCartRepositoryImpl(
    private val db: FirebaseFirestore,
): ShoppingCartRepository {
    override fun incrementQuantity(heroId: String) = flow {
        try {
            emit(Result.Loading)
            val heroIdRef = db.collection("shoppingCart").document(heroId)
            heroIdRef.update("quantity", FieldValue.increment(1)).await()
            emit(Result.Success(true))
        } catch (e: Exception) {
            emit(Result.Failure(e))
        }
    }

    override fun decrementQuantity(heroId: String) = flow {
        try {
            emit(Result.Loading)
            val heroIdRef = db.collection("shoppingCart").document(heroId)
            heroIdRef.update("quantity", FieldValue.increment(-1)).await()
            emit(Result.Success(true))
        } catch (e: Exception) {
            emit(Result.Failure(e))
        }
    }
}

可行的代码复用方案

1. 提取独立的数量操作组件(推荐)

把增减数量的逻辑抽成一个独立的Repository/UseCase,比如HeroQuantityRepository,专注处理数量变更的核心逻辑,不绑定任何页面的业务。这样所有需要数量操作的ViewModel只需要注入这个组件,无需依赖其他页面的Repository。

第一步:定义独立的数量操作接口与实现

interface HeroQuantityRepository {
    fun incrementQuantity(heroId: String): Flow<Result<Boolean>>
    fun decrementQuantity(heroId: String): Flow<Result<Boolean>>
}

class HeroQuantityRepositoryImpl(
    private val db: FirebaseFirestore
): HeroQuantityRepository {
    override fun incrementQuantity(heroId: String) = flow {
        try {
            emit(Result.Loading)
            val cartRef = db.collection("shoppingCart").document(heroId)
            cartRef.update("quantity", FieldValue.increment(1)).await()
            emit(Result.Success(true))
        } catch (e: Exception) {
            emit(Result.Failure(e))
        }
    }

    override fun decrementQuantity(heroId: String) = flow {
        try {
            emit(Result.Loading)
            val cartRef = db.collection("shoppingCart").document(heroId)
            cartRef.update("quantity", FieldValue.increment(-1)).await()
            emit(Result.Success(true))
        } catch (e: Exception) {
            emit(Result.Failure(e))
        }
    }
}

第二步:在ViewModel中注入使用

以HeroDetailsViewModel为例:

class HeroDetailsViewModel @Inject constructor(
    private val heroDetailsRepo: HeroDetailsRepository,
    private val quantityRepo: HeroQuantityRepository // 注入独立组件
): ViewModel() {
    var incrementQuantityResult by mutableStateOf<Result<Boolean>>(Result.Success(false))
        private set

    fun incrementHeroQuantity(heroId: String) = viewModelScope.launch {
        quantityRepo.incrementQuantity(heroId).collect {
            incrementQuantityResult = it
        }
    }

    // 其他HeroDetails专属业务逻辑...
}

这种方式完全解耦,每个ViewModel只依赖自身业务需要的组件,避免跨页面Repository的不合理依赖。

2. 使用抽象类封装通用逻辑

如果不想新增独立组件,可以定义一个抽象基础Repository,把增减数量的逻辑放在里面,让其他Repository继承这个抽象类:

abstract class BaseHeroRepository(protected val db: FirebaseFirestore) {
    fun incrementQuantity(heroId: String) = flow {
        try {
            emit(Result.Loading)
            val cartRef = db.collection("shoppingCart").document(heroId)
            cartRef.update("quantity", FieldValue.increment(1)).await()
            emit(Result.Success(true))
        } catch (e: Exception) {
            emit(Result.Failure(e))
        }
    }

    fun decrementQuantity(heroId: String) = flow {
        try {
            emit(Result.Loading)
            val cartRef = db.collection("shoppingCart").document(heroId)
            cartRef.update("quantity", FieldValue.increment(-1)).await()
            emit(Result.Success(true))
        } catch (e: Exception) {
            emit(Result.Failure(e))
        }
    }
}

// 让各业务Repository继承
class ShoppingCartRepositoryImpl(db: FirebaseFirestore): BaseHeroRepository(db), ShoppingCartRepository {
    // ShoppingCart专属业务逻辑...
}

class HeroDetailsRepositoryImpl(db: FirebaseFirestore): BaseHeroRepository(db), HeroDetailsRepository {
    // HeroDetails专属业务逻辑...
}

ViewModel层面也可以用类似的抽象基类封装重复的状态收集逻辑,进一步减少VM中的代码重复。

3. 关于“把函数放到单个Repository注入到其他VM”的合理性

这种方式不推荐,会造成不必要的依赖耦合:HeroDetailsViewModel本来只需要处理英雄详情业务,却要依赖ShoppingCartRepository,违反了单一职责原则,后续维护时容易出现逻辑混淆,也不利于单元测试(需要Mock无关的ShoppingCartRepository)。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 01:55:20