kotlin adapters
¿Cómo delegar la implementación a una propiedad en Kotlin? (2)
Kotlin me permite implementar una interfaz al delegar en un argumento de constructor primario como este:
class Foo(xs : ArrayList<Int>) : List<Int> by xs { }
Pero esto exhibe el implementador de respaldo al usuario. Delegar a un anónimo también parece estar bien:
class Foo() : List<Int> by ArrayList<Int>() { }
Esto oculta los detalles de la implementación, pero perdemos el acceso a características no proporcionadas por la interfaz, que en este caso es mutability.
Por lo tanto, me gustaría delegar la implementación a una propiedad que no está en el constructor primario. Lo que me gustaría tener es similar a
class Foo() : List<Int> by xs {
val xs : List<Int> = ArrayList<Int>()
}
que no se compila.
¿Es posible tener una propiedad definida explícitamente en el cuerpo de la clase y aún así poder delegar su implementación?
Ampliando la respuesta de Alexander Udalov, se me ocurrió una solución utilizando una clase base privada
private open class FooBase(protected val xs : MutableList<Int>) : List<Int> by xs { }
class Foo() : FooBase(ArrayList()) {
fun bar() {
xs.add(5)
}
}
Ahora puedo tener acceso a la propiedad que respalda la implementación de mi interfaz, pero no estoy restringido a las operaciones proporcionadas por esa interfaz mientras sigo ocultando la implementación real al usuario.
Nota: Aunque funciona, recibo la siguiente advertencia de IntelliJ IDEA 15 CE que surge de la inspección EXPOSED_SUPER_CLASS
: Desaprobada: la subclase de visibilidad efectiva "pública" debe ser igual o menos permisiva que su superclase de visibilidad efectiva "privada" . No estoy muy seguro de lo que significa la parte obsoleta aquí: si la advertencia se eliminará en el futuro o si no se compilará en algún momento. De todos modos, no tenemos que usar una clase private open
, abstract
o simplemente open
, porque incluso si el usuario tiene permiso para crear una instancia de FooBase
, no hay mucho que pueda hacer con ella.
Actualizar:
Actualmente existe una solución simple y compacta que no utiliza ningún comportamiento sospechoso:
class Foo private constructor(private val xs: ArrayList<Int>) : List<Int> by xs {
constructor() : this(ArrayList<Int>()) { }
}
Esto no es actualmente posible. La expresión en la cláusula by
se calcula solo una vez antes de la construcción de la clase, por lo que no puede hacer referencia a los símbolos de esa clase.
Hay una solicitud en el rastreador de problemas para permitir esto, aunque casi con toda seguridad no será compatible con Kotlin 1.0.
Una solución divertida que a veces funciona es hacer que la propiedad que desea que sea un delegado, un parámetro de constructor con el valor predeterminado. De esa manera, será accesible tanto en la cláusula by
como en el cuerpo de la clase:
class Foo(val xs: List<Int> = ArrayList<Int>()) : List<Int> by xs {
fun bar() {
println(xs)
}
}
Tenga en cuenta que xs
en by xs
todavía se calcula solo una vez aquí, por lo que incluso si xs
es una propiedad var
, solo se utilizará el valor predeterminado proporcionado en el constructor. No es una solución universal, pero a veces puede ayudar.