如何在 Kotlin 中为两个数据类创建可重用的方法?

问题描述 投票:0回答:1

我正在阅读《TDD 解释》一书来学习 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)
}

正如你所看到的,一美元相乘就是一美元,法郎也是如此,这就是我想要的逻辑。不过我想尝试制作一个可重用的函数来减少重复的逻辑。我不确定我是否走在正确的道路上,但我想出了这个:

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个回答
0
投票

通用可重用函数的问题在于,它需要创建货币类型的新实例,而不知道要构造哪种具体类型。

一种解决方案可能是创建一个接口

Money
,由
Dollar
Franc
实现。接口需要知道其“自身”类型,以便
times
返回正确的货币。 Kotlin 不支持 self-types,但支持递归类型参数,可以在一定程度上模拟 self-types。

接口需要知道如何构造实现类型的实例,因此需要定义一个“构造”函数。要在其中编写

times
函数,您还需要授予它对
amount
的访问权限(因此该属性不能是私有的)。

类似这样的:

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

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

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

您还可以将

amount
属性保留为私有,并通过使用抽象类来隐藏构造函数,并将它们作为值传递给其构造函数。看起来像这样:

abstract class Money<T : Money<T>>(private val amount: Int, private val construct: (Int) -> T) {
    fun times(multiplier: Int): T = construct(amount * multiplier)
}

data class Dollar(private val amount: Int) : Money<Dollar>(amount, ::Dollar)
data class Franc(private val amount: Int) : Money<Franc>(amount, ::Franc)
© www.soinside.com 2019 - 2024. All rights reserved.