Vengo de Java y soy nuevo en Kotlin. Estoy tratando de crear DSL declarativo para fines de prueba unitaria. Aquí está mi intento de crear un método de construcción genérico para construir cualquier objeto modelo con sintaxis declarativa.
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 } } Sin embargo, la función de having devuelve Any tipo con referencia no resuelta a with función. Podría extraer el objeto anónimo a su propia clase para compilarlo, pero se sentiría redundante. ¿Es posible recurrir a la inferencia de tipo con funciones que devuelven instancias de un object ?
Además, no estoy seguro de si estoy usando genéricos de forma idiomática aquí.
El motivo por el cual el tipo de retorno de la función de having se infiere como Any se explica en mi respuesta aquí . Básicamente, tendrá que hacer que having private para que infiera el tipo de retorno deseado. Obviamente, esa no es una solución viable para su unidad de prueba DSL, que será utilizada por el código en otros archivos.
Puede considerar eliminar la palabra having y declarar directamente with como una función de extensión/infijo en KClass .
Función de extensión:
fun <T: Any> KClass<T>.with(fn: T.() -> Unit) = this.createInstance().apply(fn) // ... Pizza::class.with { size = Size.NORMAL cheese = Cheese.MOZZARELLA }Función de infijo:
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 } }En lugar de devolver un object de tipo no especificado, puede devolver un objeto envolvente, que declara la función with y contiene el objeto de clase original.
De esta manera, obtiene seguridad de tipo, al mismo tiempo having conserva su DSL de estilo.
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())Usándolo, se ve así:
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) }Sin embargo, tenga en cuenta que tanto este como su enfoque original tienen una advertencia. Solo funcionan para clases con constructores, que no requieren ningún parámetro.
Consulte la documentación de KClass<T>.createInstance() :
Crea una nueva instancia de la clase, llamando a un constructor que no tiene parámetros o todos los parámetros son opcionales (consulte KParameter.isOptional). Si no hay o hay muchos constructores de este tipo, se lanza una excepción.
Cuando no sea necesario having parte del DSL, consulte la solución de @Sweeper .