繁体   English   中英

Kotlin中如何动态组合接口?

[英]How to dynamically combine interfaces in Kotlin?

我不确定如何以更正确的方式表达这个问题,或者如何想出一个不那么做作的例子。

假设我有一个描述某些字段的界面(在我的示例中为Data )。

然后我想对其应用一些转换(在我的示例中对字段进行求和并相乘)。 理想情况下,这应该类似于应用程序用户在运行时可配置的数据管道(例如,通过配置文件)。 我还为通过每个转换( SummedDataMultipliedData )添加到我的 object 的属性定义接口。

我还定义了一些实现这些接口以及原始Data接口的类( DataSummerDataMultiplier ),因此它们可以“级联”并一个接一个地应用。 在我的示例中,这些非常简单,但您可以想象它们懒惰地调用外部 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 和它的吸气剂一样好,我们必须:

  1. 使用反射
  2. 隐式地(没有编译时错误,但运行时)限制混合,可以根据从data: Data (或没有任何来源)。 在以下实现中,这些 mixin 是:
    1. 单例(又名 object 类)
    2. 具有不超过一个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))

另外我相信您的DataSummerDataMultiplier类可以简化:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM