在Kotlin中从函数返回匿名对象并推断类型

3

我从Java转到Kotlin,对Kotlin还不太熟悉。我正在尝试创建用于单元测试目的的声明式DSL。这是我创建通用构建器方法以使用声明式语法构建任何模型对象的尝试。

fun <T> having(t: Class<T>) = object {
    infix fun with(fn: T.() -> Unit) = t.newInstance().apply(fn)
}

fun test() {

    having(Pizza::class.java) with {
        size = Size.NORMAL
        cheese = Cheese.MOZARELLA
    }
}

然而,having 函数返回 Any 类型,并且与 with 函数存在未解决的引用。我可以将匿名对象提取到它自己的类中以使其编译,但这会感觉很冗余。是否有可能使用返回 object 实例的函数进行类型推断?
此外,我不确定我在这里是否以惯用方式使用泛型。

1
这本质上是与此问题相同的问题。解决方法是将having变成一个private函数,但我认为这对您来说可能不太理想。我建议您为此对象创建一个新类。 - Sweeper
1
此外,这难道不就是一个带有额外步骤的 Pizza().apply { ... } 吗?你是否考虑过其他语法形式,比如 Pizza::class.with { ... } 或者 Pizza::class with { ... }?基本上,我不明白 having 调用的目的是什么。 - Sweeper
@Sweeper 我喜欢 Pizza::class.with { ... } 的语法,但我不确定如何将泛型扩展方法添加到 class 实例化类中。 - Tuomas Toivonen
2个回答

3
having函数的返回类型被推断为Any的原因在我的这篇答案中有解释。基本上,你需要将having声明为private,以使其推断出期望的返回类型。显然,这对于你的单元测试DSL来说不是可行的解决方案,因为该DSL将被其他文件中的代码使用。

您可以考虑删除单词having,而直接将with声明为KClass的扩展/中缀函数。

扩展函数:

fun <T: Any> KClass<T>.with(fn: T.() -> Unit) = this.createInstance().apply(fn)

// ...

Pizza::class.with {
    size = Size.NORMAL
    cheese = Cheese.MOZZARELLA
}

中缀函数:

infix fun <T: Any> KClass<T>.with(fn: T.() -> Unit) = this.createInstance().apply(fn)

fun main() {

    Pizza::class with {
        size = Size.NORMAL
        cheese = Cheese.MOZZARELLA
    }
}

1
不要返回未指定类型的对象,而是返回一个包装对象,该对象声明了“with”函数并包含原始类对象。
这样可以获得类型安全性,同时保留您的“having”风格DSL。
data class WrappedInstance<T>(val data: T) {
    infix fun with(applyFn: T.() -> Unit): T = data.also(applyFn)
}

fun <T : Any> having(kClass: KClass<T>): WrappedInstance<T> =
    WrappedInstance(kClass.createInstance())

使用它,看起来像这样:
data class Pizza(var size: Int = 1, var cheese: Int = 1)

fun test() {
    val pizza: Pizza = having(Pizza::class) with {
        size = 3
        cheese = 5
    }
    
    println(pizza.size)
    println(pizza.cheese)
}

请注意,这种方法和您最初的方法都有一个警告。它们仅适用于不需要任何参数的构造函数的类。
请参阅 KClass<T>.createInstance() 的文档:

创建该类的新实例,调用一个没有参数或其所有参数都是可选的构造函数(请参阅 KParameter.isOptional)。如果没有或有多个这样的构造函数,则会抛出异常。


当 DSL 中不需要 `having` 部分时,请查看 @Sweeper 的解决方案

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接