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

如何在Kotlin中为两个数据类实现可复用的times方法?

问题描述

我正在通过《TDD Explained》一书学习Kotlin,目前编写了如下代码:

data class Dollar (private val amount: Int) {
    fun times(multiplier: Int) = Dollar(amount = this.amount * multiplier)
}

data class Franc (private val amount: Int) {
    fun times(multiplier: Int) = Franc(amount = this.amount * multiplier)
}

如你所见,Dollar相乘返回Dollar,Franc相乘返回Franc,这是我需要的逻辑,但我希望实现可复用函数以减少重复代码。我尝试了以下写法,但不确定是否正确:

data class Dollar (val amount: Int)
data class Franc (val amount: Int)

fun Dollar.times(multiplier: Int) = Dollar(amount = this.amount * multiplier)
fun Franc.times(multiplier: Int) = Franc(amount = this.amount * multiplier)

请问如何用Kotlin的惯用方式复用该逻辑?以下是当前已通过的测试代码:

@Test
fun testDollarMultiplication() {
    val five = Dollar(5)
    assertEquals(Dollar(10), five.times(2))
    assertEquals(Dollar(15), five.times(3))
}

@Test
fun testFrancMultiplication() {
    val five = Franc(5)
    assertEquals(Franc(10), five.times(2))
    assertEquals(Franc(15), five.times(3))
}

@Test
fun testEquality() {
    assertTrue(Dollar(5).equals(Dollar(5)))
    assertFalse(Dollar(5).equals(Dollar(10)))
    assertTrue(Franc(5).equals(Franc(5)))
    assertFalse(Franc(5).equals(Franc(10)))
    assertFalse(Dollar(5).equals("hi"))
}
解决方案

在Kotlin中,针对这类“数值运算返回自身类型”的场景,有几种符合惯用风格的复用方式,以下是具体实现:

1. 你的扩展函数写法完全正确

首先明确:你把times写成扩展函数的方式是Kotlin中复用类方法的常用手段,完全符合惯用风格。这种写法无需修改原有data class结构,保持类的简洁性,同时实现了逻辑复用,对于当前只有两个类的场景,已经足够高效且易维护。

2. 泛型接口+默认方法(适合后续扩展更多货币类)

如果后续需要新增更多类似的货币类,推荐用F-bounded多态的泛型接口来抽象公共契约,避免重复代码:

interface Money<T : Money<T>> {
    val amount: Int
    fun times(multiplier: Int): T
}

data class Dollar(override val amount: Int) : Money<Dollar> {
    override fun times(multiplier: Int): Dollar = Dollar(amount * multiplier)
}

data class Franc(override val amount: Int) : Money<Franc> {
    override fun times(multiplier: Int): Franc = Franc(amount * multiplier)
}

这种方式通过泛型约束T : Money<T>确保times方法返回当前类的实例,把公共属性和方法契约抽象到接口中,子类仅需实现具体的实例化逻辑,结构清晰且扩展性强。

3. 接口+泛型扩展函数(完全复用逻辑)

如果想彻底复用times的实现逻辑,无需子类重复编写实例化代码,可以结合接口和带reified参数的扩展函数:

interface Valuable {
    val amount: Int
}

inline fun <reified T : Valuable> T.times(multiplier: Int): T {
    // 依赖子类有Int参数的构造函数(data class默认满足)
    return T::class.java.getConstructor(Int::class.java).newInstance(amount * multiplier)
}

data class Dollar(override val amount: Int) : Valuable
data class Franc(override val amount: Int) : Valuable

这种写法把times逻辑完全抽离,子类仅需实现Valuable接口暴露amount即可。注意这里用到了反射创建实例,对于测试场景完全适用,若追求极致性能,可结合注解或工厂模式替代反射。

以上三种方式都能通过你提供的测试代码,可根据后续扩展需求选择合适的方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 01:06:05