[英]How to dynamically combine interfaces in Kotlin?
我不确定如何以更正确的方式表达这个问题,或者如何想出一个不那么做作的例子。
假设我有一个描述某些字段的界面(在我的示例中为Data
)。
然后我想对其应用一些转换(在我的示例中对字段进行求和并相乘)。 理想情况下,这应该类似于应用程序用户在运行时可配置的数据管道(例如,通过配置文件)。 我还为通过每个转换( SummedData
和MultipliedData
)添加到我的 object 的属性定义接口。
我还定义了一些实现这些接口以及原始Data
接口的类( DataSummer
和DataMultiplier
),因此它们可以“级联”并一个接一个地应用。 在我的示例中,这些非常简单,但您可以想象它们懒惰地调用外部 API 或进行一些昂贵的处理。
一切正常,除了每个后续转换都会删除前一个转换的类型信息(在我的示例中, data is SummedData
始终为真, data is MultipliedData
始终为假,尽管它们都应用于原始对象)。 我想要的是无论我应用转换的顺序如何,这两项检查都是真实的。
我知道如何用 Python 等更动态的语言来做到这一点,但我想知道 Kotlin 是否可以做类似的事情。
代码示例:
interface Data {
val field1 : Int
val field2 : Int
}
interface SummedData {
val fSum : Int
}
interface MultipliedData {
val fProd : Int
}
data class DataSummer(private val iData : Data, private val iSum : SummedData) : Data by iData, SummedData by iSum{
constructor(data : Data) : this(
data,
object:SummedData {override val fSum:Int = data.field1 + data.field2}
)
}
data class DataMultiplier(private val iData : Data, private val iMul : MultipliedData) : Data by iData, MultipliedData by iMul{
constructor(data : Data) : this(
data,
object:MultipliedData {override val fProd:Int = data.field1 * data.field2}
)
}
fun main() {
var data:Data = object : Data {
override val field1 = 2
override val field2 = 3}
data = DataMultiplier(data)
data = DataSummer(data)
//Always false :(
if (data is MultipliedData){
println("Product of ${data.field1} and ${data.field2} is ${data.fProd}" )
}
if (data is SummedData){
println("Sum of ${data.field1} and ${data.field2} is ${data.fSum}" )
}
}
提前感谢您的回答!
后续变换去掉前一个的类型信息
如果要保留此信息,则需要显式存储它:
//abstract class is used here instead of interface to introduce its property as private
//pass thread-safe implementation of MutableList into constructor if you are about to add mixins in multiple threads
abstract class DataWithMixins(private val mixins: MutableList<Any> = ArrayList()) : Data {
fun addMixin(mixin: Any): DataWithMixins {
mixins.add(mixin)
return this //to allow chaining calls
}
inline fun <reified T : Any> getMixin(): T? = getMixinOfType(T::class) as? T
//mediator-function to access non-public API from public-API inline function
fun getMixinOfType(type: KClass<*>): Any? = mixins.find { type.isInstance(it) }
}
用法:
fun main() {
val data = object : DataWithMixins() {
override val field1 = 2
override val field2 = 3
}
data.addMixin(DataSummer(data))
.addMixin(DataMultiplier(data))
//get mixin of interface type
data.getMixin<SummedData>()?.run {
println("Sum of ${data.field1} and ${data.field2} is $fSum")
}
data.getMixin<MultipliedData>()?.run {
println("Product of ${data.field1} and ${data.field2} is $fProd")
}
//get mixin of class type, now we can access field1 & field2 directly
data.getMixin<DataSummer>()?.run {
println("Sum of $field1 and $field2 is $fSum")
}
data.getMixin<DataMultiplier>()?.run {
println("Product of $field1 and $field2 is $fProd")
}
}
为了使混合的 API 和它的吸气剂一样好,我们必须:
data: Data
(或没有任何来源)。 在以下实现中,这些 mixin 是:
Data
参数的构造函数的类(因为我们不会使用它的默认值,但会传递我们自己的)并且没有其他参数或所有其他参数都是可选的(这包括无参数构造函数)inline fun <reified T : Any> DataWithMixins.addMixin(): DataWithMixins {
val objectInstance = T::class.objectInstance
val mixin = when {
objectInstance != null -> objectInstance
else -> {
//there is no more than one Data parameter
//AND
//there is no more than one mandatory parameter and it is Data
var niceConstructor: KFunction<T>? = null
var dataParameter: KParameter? = null
constructorsLoop@
for (constructor in T::class.constructors) {
dataParameter = null
var mandatoryParametersCounter = 0
for (param in constructor.parameters) {
val isMandatory = !param.isOptional
val isData = param.type.classifier == Data::class
if (isData) {
dataParameter = if (dataParameter == null) param else continue@constructorsLoop
if (isMandatory && ++mandatoryParametersCounter > 1) continue@constructorsLoop
} else if (isMandatory) continue@constructorsLoop
}
if (mandatoryParametersCounter == 0 || dataParameter != null) {
niceConstructor = constructor
break@constructorsLoop
}
}
if (niceConstructor == null) error("${T::class} can't be auto-instantiated as a mixin for Data")
if (dataParameter == null) {
niceConstructor.call()
} else {
niceConstructor.callBy(mapOf(dataParameter to this))
}
}
}
//can't access non-public API from public-API inline function directly, so will use this method as mediator
return addMixin(mixin)
}
用法:
data.addMixin<DataSummer>()
.addMixin<DataMultiplier>()
//instead of
// data.addMixin(DataSummer(data))
// .addMixin(DataMultiplier(data))
另外我相信您的DataSummer
和DataMultiplier
类可以简化:
data class DataSummer(private val iData: Data) : Data by iData, SummedData {
override val fSum: Int = iData.field1 + iData.field2
}
data class DataMultiplier(private val iData: Data) : Data by iData, MultipliedData {
override val fProd: Int = iData.field1 * iData.field2
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.