Kotlin 以多态方式处理许多类似的单态类
我有一个库,它生成了一大堆外观非常相似的Java类。为了简洁起见,让我们在Kotlin中指定类似的内容:Kotlin 以多态方式处理许多类似的单态类,kotlin,Kotlin,我有一个库,它生成了一大堆外观非常相似的Java类。为了简洁起见,让我们在Kotlin中指定类似的内容: data class A(val commonParam1: String, val commonParam2: String, val specialParam: Int) data class B(val commonParam1: String, val commonParam2: String, val specialParam: String) data class C(val c
data class A(val commonParam1: String, val commonParam2: String, val specialParam: Int)
data class B(val commonParam1: String, val commonParam2: String, val specialParam: String)
data class C(val commonParam1: String, val commonParam2: String, val specialParam: Boolean)
data class D(val commonParam1: String, val commonParam2: String, val specialParam: Float)
显然,最好生成一个通用类或接口:
data class G<T>(val commonParam1: String, val commonParam2: String, val specialParam: T)
数据类G(val commonParam1:String,val commonParam2:String,val specialParam:T)
但是库不会生成这种代码。我有几十个这样的类,它们在不同的地方生成,所以这是一个m×n的情况。不幸的是,这些类没有共享一个公共接口(我可以使用)。因此,在我看来,有五个选项可以解决这个问题:
- 编写与任何生成的类一起工作的所有代码m×n次。这是一大堆复制品,但它一点也不复杂,而且是安全的
- 使用反射。首先,生成这些类背后的想法是增加类型安全性。反思会再次把它扔出窗外,所以它会让人觉得适得其反
- 对我自己的代码使用代码生成。这感觉像是一种有点笨手笨脚的方法,我的团队没有代码生成的经验
- 使用。那太好了,但我可能会接受目前存在的黑客攻击。我仍然需要提供大量的boiler plate,因为我必须为这些类中的每一个提供接口证据
- 使用准引号,但我认为我还不应该在生产中使用Arrow Meta
在这个具体的例子中,我的Graphql模式有很多突变,它们采用了非常相似的
mutationput
。我们不直接控制模式的形状。由于每个突变也有其自己的输入和输出类型,因此构建这些突变并解释其输出是一项繁重的工作,即使我大量使用片段,也会产生大量重复的代码,因为即使提取片段的代码也必须重复
这在Graphql中通常不是问题,因为大部分都是在JS或TS中完成的,两者都有duck类型。Kotlin没有duck类型。我在这里列出了多个选项,其中一些选项可以组合使用以重用代码并生成最少的繁琐代码 与默认实现的接口 您可以使用具有默认函数实现的接口来提供公共基础:
interface CommonDataInterface<T> {
val commonParam1: String
val commonParam2: String
val specialParam: T
fun foo() { ...implementation here }
fun bar() { ...implementation here }
fun zoo(x: T): T { ...implementation here }
}
// concrete variations, using common interface functions implemented above:
data class A(override val commonParam1: String,
override val commonParam2: String,
override val specialParam: Int)
: CommonDataInterface<Int>
data class B(override val commonParam1: String,
override val commonParam2: String,
override val specialParam: String)
: CommonDataInterface<String>
data class C(override val commonParam1: String,
override val commonParam2: String,
override val specialParam: Boolean)
: CommonDataInterface<Boolean>
data class D(override val commonParam1: String,
override val commonParam2: String,
override val specialParam: Float)
: CommonDataInterface<Float>
但是上面提到的不允许任何公共函数使用specialParam
,它将按照专门化来实现
使用注释处理器生成类的置换 您可以编写自己的注释处理器,这是代码生成或其他编译时操作的一种形式,向特殊类添加注释,并让注释处理器生成字节码中的所有排列。然后,它可以被烘焙到您的编译过程中,神奇地,这些额外的类就会出现 您需要了解Kotlin是如何进行注释处理的,以及该主题在某些地方的一般情况。此外,post Kotlin 1.4可能是一个编译器插件API,可以帮助编写完成类似工作的插件 有关注释处理的一些链接表明,使用注释处理并不是那么困难:
使用Kotlin的代码生成库生成Kotlin代码
查看Kotlin代码生成的一个简单方法是库。这是一种程序化的代码生成方法,即使对于没有代码生成经验的团队来说也很容易。这可以与前面提到的关于如何构造类的变体一起使用。它还可以与注释处理一起使用,作为编译过程中触发代码生成的方法。这些类只是空数据容器吗?没有方法或逻辑?它们是否需要是数据类(因为您对数据类继承有限制)?这些问题很重要,因为它们会影响可能的解决方案。它们确实有一些逻辑/方法,但在大多数情况下,它们是数据传输对象。好的,我在回答中给出了一些不同的解决方案,以及从注释处理器生成代码的选项。我不控制基本数据类,因此无法添加接口。但是我认为我可能能够使用代码生成来复制基类,并让它们实现公共接口。也许,我得去看看。
class G<T>(val commonParam1: String, val commonParam2: String, val specialParam: T) {
fun foo() { ...implementation here }
fun bar() { ...implementation here }
fun zoo(x: T): T { ...implementation here }
}
// concrete variations, using common functions from base class
class A(commonParam1: String, commonParam2: String, specialParam: Int)
: G<Int>(commonParam1, commonParam2, specialParam)
class B(commonParam1: String, commonParam2: String, specialParam: String)
: G<String>(commonParam1, commonParam2, specialParam)
class C(commonParam1: String, commonParam2: String, specialParam: Boolean)
: G<Boolean>(commonParam1, commonParam2, specialParam)
class D(commonParam1: String, commonParam2: String, specialParam: Float)
: G<Float>(commonParam1, commonParam2)
interface CommonDataInterface {
val commonParam1: String
val commonParam2: String
fun foo()
fun bar()
}
data class CommonData(override val commonParam1: String,
override val commonParam2: String)
: CommonDataInterface {
override fun foo() { ...implementation here }
override fun bar() { ...implementation here }
}
// concrete implementations using common functions via automatic delegation
data class A(private val common: CommonDataInterface,
val specialParam: Int) : CommonDataInterface by common {
fun zoo(x: Int): Int { ...implementation here using specialParam }
}
data class B(private val common: CommonDataInterface,
val specialParam: String) : CommonDataInterface by common {
fun other(y: String): String { ...implementation here using specialParam }
}
data class C(private val common: CommonDataInterface,
val specialParam: Boolean) : CommonDataInterface by common
data class D(private val common: CommonDataInterface,
val specialParam: Float) : CommonDataInterface by common