Kotlin admite parámetros de herencia a través de la palabra clave open, pero la recomendación principal del lenguaje es optar por la composición en lugar de la herencia. Esto permite crear sistemas más flexibles y expansibles, evitando la fragilidad de las jerarquías y los problemas relacionados con la herencia profunda.
La composición consiste en incluir un objeto del tipo necesario como campo de una clase y delegarle el trabajo, en lugar de heredar su implementación. Kotlin facilita el patrón de delegación con la palabra clave by, lo que permite delegar automáticamente la implementación de interfaces a objetos.
Ejemplo del patrón de delegación:
interface Logger { fun log(message: String) } class ConsoleLogger : Logger { override fun log(message: String) = println(message) } class Service(private val logger: Logger) : Logger by logger { fun doAction() { log("Acción realizada") } } fun main() { val service = Service(ConsoleLogger()) service.doAction() // Imprimirá: Acción realizada }
Este enfoque simplifica la reutilización del código y hace que la lógica sea más modular.
"¿Puede una data class heredar de otra clase, por ejemplo, de una clase abstracta?"
data class en Kotlin no puede heredar de otra clase (excepto interfaces), ya que data class siempre es final. La excepción son las interfaces, que se pueden implementar.Ejemplo:
abstract class Base(val name: String) data class Derived(val age: Int, val name: String) : Base(name) // Error de compilación: data class no puede extender la clase Base
Pero es posible:
interface User data class Admin(val name: String, val rights: List<String>) : User
Historia
En el proyecto decidieron heredar varios servicios de una clase abstracta común para implementar una lógica repetitiva. Al final, resultó en múltiples niveles de herencia, complicando la depuración y apareciendo problemas con las pruebas. Después de cambiar a composición y delegación (a través de interfaces e inyección de dependencias), logramos simplificar el código, hacerlo más modular y aumentar la cobertura de pruebas.
Historia
Un desarrollador principiante intentó extender una data class utilizando otra clase para agregar funcionalidad común. El código no compilaba, pero el programador no pudo entender la razón durante mucho tiempo (las limitaciones de la data class en Kotlin).
Historia
En un proyecto con lógica de registro extensa, decidimos extraer la funcionalidad de registro a una clase base. Sin embargo, a medida que el sistema creció, algunos servicios requerían una implementación de registro diferente. Tuvimos que refactorizar utilizando la interfaz Logger y composición a través de delegación, lo que simplificó significativamente la arquitectura.