Interfaces: herencia de interfaces

Las interfaces pueden especializarse mediante un sistema de herencia similar al que utilizan las clases, de tal manera que podemos tener interfaces que heredan los campos y propiedades de otras interfaces superiores en la jerarquía.

Cuando trabajamos con TypeScript, las interfaces nos permiten representar jerarquías y especializaciones de manera clara y estructurada. Un concepto fundamental que podemos aplicar es la herencia entre interfaces, que nos ayuda a definir relaciones jerárquicas y a extender funcionalidades de forma ordenada.

Imaginemos que tenemos una interfaz base llamada Vehicle, que representa un vehículo genérico. En ella podemos declarar propiedades comunes, como el fabricante, que podría ser una cadena de texto. A partir de esta interfaz base, podemos crear interfaces más especializadas, como VehiculoTerrestre y VehiculoMaritimo, que extienden a Vehicle. Esto significa que heredan todas las propiedades y métodos de Vehicle, pero además pueden añadir características propias.

Por ejemplo, VehiculoTerrestre podría incluir un método conducir que no está en la interfaz base, mientras que VehiculoMaritimo podría tener métodos específicos como sonarSirena o encenderChimenea. La palabra clave extends es la que usamos para indicar esta relación de herencia entre interfaces.

interface Vehicle {
  readonly fabricante: string;
  arrancarMotor(): void;
  repostar(): void;
  detenerMotor(): void;
}

interface VehiculoTerrestre extends Vehicle {
  conducir(): void;
}

interface VehiculoMaritimo extends Vehicle {
  sonarSirena(): void;
  encenderChimenea(): void;
  detenerChimenea(): void;
}

Una vez definidas estas interfaces, podemos implementar una clase que represente un vehículo concreto, como un OpelCorsa, que implemente la interfaz VehiculoTerrestre. Al hacerlo, la clase debe proporcionar implementaciones para todos los métodos declarados en VehiculoTerrestre y también en Vehicle, ya que esta última es la interfaz base.

class OpelCorsa implements VehiculoTerrestre {
  readonly fabricante = "Opel";

  arrancarMotor(): void {
    console.log("Motor arrancando: brum brum");
  }

  repostar(): void {
    console.log("Echando 20 euros de gasolina");
  }

  detenerMotor(): void {
    console.log("Motor detenido: turururu... silencio");
  }

  conducir(): void {
    console.log("Conduciendo el Opel Corsa");
  }
}

Este enfoque nos permite encapsular la información y trabajar con abstracciones. Por ejemplo, podemos crear funciones que acepten parámetros de tipo VehiculoTerrestre sin importar si son un OpelCorsa, un patinete eléctrico o cualquier otro vehículo terrestre. Lo importante es que cumplan con la interfaz y tengan los métodos esperados, como conducir o repostar.

function procesarVehiculoTerrestre(vehiculo: VehiculoTerrestre) {
  vehiculo.arrancarMotor();
  vehiculo.conducir();
  vehiculo.repostar();
  vehiculo.detenerMotor();
}

const miCoche = new OpelCorsa();
procesarVehiculoTerrestre(miCoche);

Si en lugar de usar VehiculoTerrestre usáramos la interfaz base Vehicle, solo tendríamos acceso a los métodos comunes, sin poder llamar a conducir, que es específico de los vehículos terrestres.

Además, es interesante comparar las interfaces con los alias de tipo (type alias). En versiones recientes de TypeScript, los alias de tipo han ganado capacidades que antes solo tenían las interfaces, como la posibilidad de extender otros tipos usando el operador de intersección &. Esto nos permite replicar jerarquías similares a las que logramos con interfaces.

Por ejemplo, podríamos definir:

type Vehicle = {
  readonly fabricante: string;
  arrancarMotor(): void;
  repostar(): void;
  detenerMotor(): void;
};

type VehiculoTerrestre = Vehicle & {
  conducir(): void;
};

Así, VehiculoTerrestre combina las propiedades y métodos de Vehicle con los suyos propios. Esta flexibilidad hace que tanto interfaces como tipos puedan ser usados para modelar jerarquías y especializaciones, aunque las interfaces siguen siendo la opción más natural para definir contratos en programación orientada a objetos.

En definitiva, la herencia y especialización de interfaces en TypeScript nos permiten diseñar sistemas más organizados y reutilizables, facilitando la abstracción y el trabajo con diferentes tipos de objetos que comparten características comunes.

Lista de reproducción
  1. 1
    Temporada 1
    5 minutos
  2. 2
    ¿Qué es TypeScript?
    11 minutos
  3. 3
    Instalando TypeScript
    8 minutos
  4. 4
    Compilando un Hola Mundo sencillo
    7 minutos
  5. 5
    Hola Mundo pero con tipos
    10 minutos
  6. 6
    Tipos: tipos primitivos
    12 minutos
  7. 7
    Tipos: tipos especiales (any, null, ...)
    10 minutos
  8. 8
    Tipos: arrays y tuplas
    11 minutos
  9. 9
    Tipos: objetos
    7 minutos
  10. 10
    Funciones: lo básico
    9 minutos
  11. 11
    Funciones: tipando funciones
    9 minutos
  12. 12
    Clases: introducción a las clases
    9 minutos
  13. 13
    Clases: creando una clase
    10 minutos
  14. 14
    Clases: modificador private
    8 minutos
  15. 15
    Clases: modificador readonly
    3 minutos
  16. 16
    Clases: Atributos virtuales con getters y setters
    10 minutos
  17. 17
    Clases: herencia
    9 minutos
  18. 18
    Clases: modificadores abstract y protected
    8 minutos
  19. 19
    Tipos alias
    6 minutos
  20. 20
    Tipos literales
    5 minutos
  21. 21
    Uniones de tipos
    7 minutos
  22. 22
    Uniones discriminantes
    7 minutos
  23. 23
    Intersecciones de tipos
    5 minutos
  24. 24
    Interfaces: introducción
    7 minutos
  25. 25
    Interfaces: modificadores y funciones
    9 minutos
  26. 26
    Interfaces: usándolas con clases
    8 minutos
  27. 27
    Interfaces: herencia de interfaces
    8 minutos
  28. 28
    Interfaces: interfaces indizadas
    5 minutos
  29. 29
    Interfaces: funciones y tipos híbridos
    5 minutos
  30. 30
    ¿Qué diferencia hay entre interfaces y tipos? (2020)
    8 minutos
  31. 31
    Casteos con as
    6 minutos
  32. 32
    instanceof y las guardas
    9 minutos
  33. 33
    Tipos enumerados
    8 minutos
  34. 34
    Valores avanzados para enumerados
    7 minutos
  35. 35
    Enumerados con valores computados
    6 minutos
  36. 36
    Genéricos en tipos
    8 minutos
  37. 37
    Múltiples genéricos y buenas prácticas
    5 minutos
  38. 38
    Genéricos en funciones
    8 minutos
  39. 39
    Genéricos con restricciones
    6 minutos
  40. 40
    Tipos de utilidad
    3 minutos
  41. 41
    Exportando módulos
    9 minutos
  42. 42
    Importando módulos
    7 minutos
  43. 43
    Export default e import asterisco
    6 minutos
  44. 44
    tsconfig
    7 minutos
  45. 45
    Módulos desde NPM
    7 minutos
  46. 46
    Arroba types y los .d.ts
    8 minutos
  47. 47
    Ejemplo (1): creando una API REST simple en TypeScript
    11 minutos
  48. 48
    Ejemplo (2): montando un servidor Express
    8 minutos
  49. 49
    Ejemplo (3): haciendo las funciones de control de datos
    11 minutos
  50. 50
    Ejemplo (4): conectando todas las piezas
    7 minutos