如何在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扩展函数特性,将业务逻辑与接口分离
- 更符合“面向接口编程”的设计思想
总结
三种方案均满足需求:
- 所有属性均为不可变
val,调用sumCounter返回新对象 - 子类仅需实现少量代码,避免逻辑重复
- 完全不依赖反射或JSON转换
内容的提问来源于stack exchange,提问作者Ícaro Pires
相关产品推荐
相关产品推荐

