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

Kotlin不使用伴生对象如何实现抽象类与实现类数据共享

问题根因

你遇到的totalAnimals值始终为0的问题,核心原因是你将其定义为了实例成员属性:每创建一个Animal子类的实例,都会生成一份独立的totalAnimals副本,默认初始值为0。只要你没有在当前调用方法的实例上调用provideTotalAnimals赋值,读取到的永远是初始值0。

把属性放到伴生对象里能解决问题,是因为伴生对象的属性属于类级别单例状态,所有该类的实例共享同一份数据。但这不是唯一方案,以下几种不依赖伴生对象的实现,在不同场景下比伴生对象更灵活、更符合设计原则:


可选实现方案

方案1:提取独立的状态持有类(优先推荐)

把共享状态抽离到单独的统计类中,通过构造函数注入给所有Animal子类,完全解耦状态持有和抽象类逻辑,也方便后续单元测试做Mock替换:

// 单一职责:仅负责存储动物相关的统计数据
class AnimalStats {
    var totalAnimals: Int = 0
}

abstract class Animal(protected val stats: AnimalStats) {
    fun provideTotalAnimals(totalAnimals: Int) {
        stats.totalAnimals = totalAnimals
    }
}

// 子类构造时传入同一个stats实例即可实现数据共享
class Dog(stats: AnimalStats): Animal(stats) {
    fun printTotalDogsAndAnimals(totalDogs: Int) {
        val total = totalDogs + stats.totalAnimals
        println("Total Animals: $total")
    }
}

// 使用示例
fun main() {
    // 可以灵活控制共享范围:全局共享/特定场景独立统计都可以实现
    val globalStats = AnimalStats()
    globalStats.totalAnimals = 100
    val dog = Dog(globalStats)
    dog.printTotalDogsAndAnimals(20) // 输出 Total Animals: 120
}

这个方案比伴生对象灵活度更高:你既可以创建全局共享的stats实例实现全类统一统计,也可以针对特定业务场景创建独立的stats实例做隔离统计,不会出现伴生对象全局状态污染的问题。

方案2:顶层属性(简单场景适用)

如果只是小型项目的简单全局统计需求,不需要多状态隔离,可以把共享属性定义为文件级顶层属性,仅对同文件下的Animal类及其子类可见:

// 顶层属性,仅当前文件内的代码可访问
private var _totalAnimals = 0

abstract class Animal {
    protected val totalAnimals: Int
        get() = _totalAnimals

    fun provideTotalAnimals(totalAnimals: Int) {
        _totalAnimals = totalAnimals
    }
}

class Dog: Animal() {
    fun printTotalDogsAndAnimals(totalDogs: Int) {
        val total = totalDogs + totalAnimals
        println("Total Animals: $total")
    }
}

这个方案代码量最少,但缺点是属性是文件级全局的,无法做不同作用域的状态隔离,不适合中大型项目使用。

方案3:属性委托(多共享属性场景适用)

如果后续有大量跨类共享的属性需求,可以提取通用的委托类封装共享逻辑,减少重复代码:

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

// 通用的单值共享委托
class SharedIntVar(private var value: Int = 0): ReadWriteProperty<Any?, Int> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): Int = value
    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
        this.value = value
    }
}

// 定义全局共享的委托实例
val animalTotalDelegate = SharedIntVar()

abstract class Animal {
    protected var totalAnimals by animalTotalDelegate

    fun provideTotalAnimals(totalAnimals: Int) {
        this.totalAnimals = totalAnimals
    }
}

class Dog: Animal() {
    fun printTotalDogsAndAnimals(totalDogs: Int) {
        val total = totalDogs + totalAnimals
        println("Total Animals: $total")
    }
}

方案选择建议
  • 如果需要灵活控制共享范围、兼顾可测试性,优先选择独立状态持有类方案,是最符合面向对象设计原则的实现
  • 如果只是单文件内的简单全局统计,不需要状态隔离,可以选择顶层属性方案,实现成本最低
  • 不建议为了共享属性强行使用嵌套类、密封类等复杂语法,只会增加不必要的代码维护成本

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 12:18:23