我正在阅读《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"))
}
通用可重用函数的问题在于,它需要创建货币类型的新实例,而不知道要构造哪种具体类型。
一种解决方案可能是创建一个接口
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)