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

如何在Kotlin中以不可变方式实现类通用行为并避免重复代码?

不可变风格重构Kotlin继承类的实现方案

问题背景

现有如下Kotlin代码,抽象类Abstract定义了通用属性与sumCounter方法,子类A、B继承该抽象类并使用可变属性counter。需求是改为不可变实现:调用sumCounter时返回新对象而非修改原对象,同时避免在A、B中重复编写方法,且不使用反射或JSON转换。

原代码:

abstract class Abstract {
    abstract val general1: String
    abstract var counter: Int // 希望替换为不可变val

    fun sumCounter(value: Int) {
        counter += value
    }
}

data class A (
    val a1: String,
    override val general1: String,
    override var counter: Int = 0,
): Abstract()

data class B (
    val b1: String,
    val b2: String,
    override val general1: String,
    override var counter: Int = 1,
): Abstract()

fun <T : Abstract> doSomething(obj: T) {
    // 期望改为:val newObj = obj.sumCounter(10)
    obj.sumCounter(10) 

    println(obj) // A(a1=a1, general1=general1, counter=10)
                 // B(b1=b1, b2=b2, general1=general1, counter=11)
}

fun myMain() {
    val a = A("a1", "general1")
    doSomething(a)

    val b = B("b1", "b2", "general1")
    doSomething(b)
}

可行方案

方案1:抽象类定义复制契约,利用Data Class的copy方法

核心思路:让子类实现一个抽象方法,负责生成新对象(复用data class自带的copy方法),抽象类中统一实现sumCounter逻辑。

修改后的代码:

// 抽象类改为全不可变属性,定义复制契约
abstract class Abstract<T : Abstract<T>> {
    abstract val general1: String
    abstract val counter: Int // 改为不可变val

    // 抽象方法:子类实现返回新对象
    protected abstract fun copyWithCounter(newCounter: Int): T

    // 统一实现sumCounter,返回新对象
    fun sumCounter(value: Int): T {
        return copyWithCounter(counter + value)
    }
}

// 子类实现copyWithCounter,直接调用data class的copy
data class A (
    val a1: String,
    override val general1: String,
    override val counter: Int = 0,
): Abstract<A>() {
    override fun copyWithCounter(newCounter: Int): A {
        return this.copy(counter = newCounter)
    }
}

data class B (
    val b1: String,
    val b2: String,
    override val general1: String,
    override val counter: Int = 1,
): Abstract<B>() {
    override fun copyWithCounter(newCounter: Int): B {
        return this.copy(counter = newCounter)
    }
}

// 修改doSomething为不可变调用
fun <T : Abstract<T>> doSomething(obj: T) {
    val newObj = obj.sumCounter(10)
    println(newObj) // 输出新对象的结果
}

fun myMain() {
    val a = A("a1", "general1")
    doSomething(a) // 输出 A(a1=a1, general1=general1, counter=10)

    val b = B("b1", "b2", "general1")
    doSomething(b) // 输出 B(b1=b1, b2=b2, general1=general1, counter=11)
}

优点:

  • 完全遵循不可变原则,所有属性均为val
  • 子类仅需一行代码实现copyWithCounter,复用data class的自动生成方法
  • 抽象类统一处理业务逻辑,避免代码重复

方案2:泛型+函数类型属性,将复制逻辑注入抽象类

核心思路:在抽象类中定义一个函数类型的属性,子类初始化时传入自身的copy逻辑,抽象类通过该函数生成新对象。

修改后的代码:

abstract class Abstract<T : Abstract<T>> {
    abstract val general1: String
    abstract val counter: Int
    // 定义函数类型属性,子类传入复制逻辑
    protected abstract val copyFunc: (Int) -> T

    fun sumCounter(value: Int): T {
        return copyFunc(counter + value)
    }
}

data class A (
    val a1: String,
    override val general1: String,
    override val counter: Int = 0,
): Abstract<A>() {
    // 初始化时注入copy逻辑
    override val copyFunc: (Int) -> A = { newCounter -> this.copy(counter = newCounter) }
}

data class B (
    val b1: String,
    val b2: String,
    override val general1: String,
    override val counter: Int = 1,
): Abstract<B>() {
    override val copyFunc: (Int) -> B = { newCounter -> this.copy(counter = newCounter) }
}

// 使用方式同方案1
fun <T : Abstract<T>> doSomething(obj: T) {
    val newObj = obj.sumCounter(10)
    println(newObj)
}

优点:

  • 代码更简洁,子类用lambda表达式实现复制逻辑,无需额外方法
  • 抽象类与子类的耦合度更低,复制逻辑完全由子类控制

方案3:使用接口+扩展函数(可选,替代抽象类)

如果不需要抽象类的其他特性,可以改用接口定义通用属性,再通过扩展函数实现sumCounter,但需要子类自行实现复制逻辑的契约。

代码示例:

interface ImmutableCounter {
    val general1: String
    val counter: Int
    fun copyWithCounter(newCounter: Int): ImmutableCounter
}

// 扩展函数实现sumCounter
fun ImmutableCounter.sumCounter(value: Int): ImmutableCounter {
    return copyWithCounter(counter + value)
}

data class A (
    val a1: String,
    override val general1: String,
    override val counter: Int = 0,
): ImmutableCounter {
    override fun copyWithCounter(newCounter: Int): A {
        return this.copy(counter = newCounter)
    }
}

data class B (
    val b1: String,
    val b2: String,
    override val general1: String,
    override val counter: Int = 1,
): ImmutableCounter {
    override fun copyWithCounter(newCounter: Int): B {
        return this.copy(counter = newCounter)
    }
}

优点:

  • 利用Kotlin扩展函数特性,将业务逻辑与接口分离
  • 更符合“面向接口编程”的设计思想

总结

三种方案均满足需求:

  1. 所有属性均为不可变val,调用sumCounter返回新对象
  2. 子类仅需实现少量代码,避免逻辑重复
  3. 完全不依赖反射或JSON转换

内容的提问来源于stack exchange,提问作者Ícaro Pires

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 22:32:45