oop - 如何在 Kotlin 中动态组合接口?
问题描述
我不确定如何以更正确的方式表达这个问题,或者如何想出一个不那么做作的例子。
假设我有一个描述某些字段的界面(Data
在我的示例中)。
然后我想对其应用一些转换(在我的示例中对字段进行求和并相乘)。理想情况下,这应该类似于应用程序用户在运行时可配置的数据管道(例如,通过配置文件)。SummedData
我还为通过每个转换 (和MultipliedData
)添加到我的对象的属性定义接口。
我还定义了一些实现这些接口以及原始接口的类 (DataSummer
和) ,因此它们可以“级联”并一个接一个地应用。在我的示例中,这些非常简单,但您可以想象它们懒惰地调用外部 API 或进行一些昂贵的处理。DataMultiplier
Data
一切正常,除了每个后续转换都会删除前一个转换的类型信息(在我的示例中,尽管它们都应用于原始对象,但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")
}
}
为了让 mixin 的 API 和它的 getter 一样好,我们必须:
- 使用反射
- 隐式(没有编译时错误,但运行时)限制 mixin,可以根据从
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
}
推荐阅读
- javascript - JavaScript 问题在对象内转换字符串(循环内)
- fullcalendar - Fullcalendar.io 按日期或扩展属性获取事件
- python - 无法使用 Scrapy 获取表数据
- r - 通过突变一些变量并保持一些变量与以前相同,将宽数据转换为 long
- python - 在不阻塞 Pygame UI 的情况下录制声音
- python-3.x - 将唯一值列表添加到现有 DataFrame 列
- laravel - 在 Laravel 7 中配置 PhpRedis
- oauth-2.0 - OAuth 令牌,限制 API 使用
- azure-devops - 在 Azure 开发操作中使用 UniversalPackage@0 任务发布成功且没有错误,但工件源中不存在包
- python - pytest - 使用仅在运行时计算的参数对测试进行参数化