ProgramaciónDesarrollador Android

Describe las diferencias entre clases abstractas e interfaces en Kotlin. ¿Cuándo aplicar cada enfoque, cuáles son las características de implementación en el lenguaje y cuáles son los matices de la herencia?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

En Kotlin, clases abstractas (abstract class) e interfaces (interface) se utilizan para declarar lógica abstracta, pero hay diferencias significativas entre ellas:

  • Una clase abstracta puede contener estados (campos), implementaciones de métodos y métodos abstractos. Una clase puede heredar solo una clase abstracta (o normal). Las clases abstractas pueden tener constructores.
  • Una interfaz puede contener declaraciones de propiedades (¡sin almacenamiento de estado!), implementaciones predeterminadas de métodos y métodos abstractos. Desde Kotlin 1.1, una interfaz puede contener implementaciones de propiedades, pero estas no pueden tener estado. Una clase puede implementar múltiples interfaces.
  • Las interfaces no pueden contener estado (backing field), mientras que las clases abstractas sí pueden.

Ejemplo de código

abstract class Animal(val name: String) { abstract fun makeSound() fun info() = println("I am $name") } interface Movable { fun move() } class Cat(name: String) : Animal(name), Movable { override fun makeSound() = println("Meow") override fun move() = println("Cat moves") }

Pregunta capciosa.

¿Puede una interfaz en Kotlin contener estado (backing field)?

La mayoría responde que se pueden declarar propiedades en una interfaz, pero aún no tienen backing field: solo getters/setters sin almacenamiento de valor. Incluso si se escribe get() = ... — el valor se calculará cada vez.

Ejemplo:

interface MyInterface { val value: Int get() = 42 // no hay almacenamiento de valor, cálculo en el vuelo }

Ejemplos de errores reales debido al desconocimiento de los matices del tema.


Historia

En un gran proyecto, un desarrollador senior intentó almacenar estado en una interfaz (por ejemplo, un contador), esperando que la propiedad var count: Int tuviera backing field. Como resultado, todas las clases que implementaban la interfaz tuvieron que implementar el almacenamiento de valor de diferentes maneras, lo que resultó en una lógica inestable (los datos se perdían si se olvidaba sobreescribir el setter).


Historia

Uno de los desarrolladores usó una clase abstracta donde se necesitaba múltiples implementaciones de diferentes estrategias de comportamiento (patrón estrategia). Como resultado, surgió un problema: solo una clase base en la jerarquía, pero se requería composición para diferentes aspectos de comportamiento. Hubo que cambiar la arquitectura a interfaces.


Historia

El equipo intentó heredar dos clases abstractas a la vez (una con lógica, otra con utilidades comunes), lo que no es posible en Kotlin. El problema se identificó ya en la fase de expansión de la aplicación. Esto resultó en una deuda técnica significativa, ya que las clases tuvieron que reorganizarse rápidamente.