Clases: modificadores abstract y protected

Cuando se usa orientación a objetos y herencia, el modificador protected limita el atributo al que se le aplique este modificador para que sólo esa clase y las clases descendientes puedan verlo. También hablo de abstract, que permite hacer clases abstractas, que no pueden ser instanciadas.

Cuando trabajamos con clases en TypeScript y queremos construir jerarquías robustas, es fundamental controlar cómo accedemos a las propiedades y métodos dentro de esas jerarquías, así como gestionar qué clases pueden ser instanciadas directamente. Para ello, dos modificadores resultan especialmente útiles: protected y abstract.

El modificador protected nos permite restringir el acceso a ciertas propiedades o métodos para que no puedan ser accedidos desde fuera de la clase, pero sí desde las clases que heredan de ella. Por ejemplo, imaginemos que tenemos una clase base Vehículo con una propiedad fabricante. Si esta propiedad es pública, cualquier código externo podría modificarla, lo que podría corromper el estado del objeto. Por otro lado, si la declaramos como private, ni siquiera las clases derivadas podrían acceder a ella, lo que limita la flexibilidad en la herencia.

Aquí es donde protected resulta ideal. Al declarar fabricante como protected, permitimos que las clases hijas, como VehículoTerrestre, puedan acceder y utilizar esa propiedad sin que esté disponible para el código externo. Esto facilita, por ejemplo, que en VehículoTerrestre definamos un método reparar que imprima un mensaje que incluya el fabricante, accediendo directamente a esa propiedad protegida.

Además, protected no solo se aplica a propiedades, sino también a métodos. Podemos definir funciones que solo sean accesibles dentro de la clase y sus descendientes, pero no desde fuera. Por ejemplo, un método llevarAlTaller declarado como protected podría ser invocado desde una clase hija como VehículoAéreo, pero no desde instancias externas.

class Vehículo {
  protected fabricante: string;

  constructor(fabricante: string) {
    this.fabricante = fabricante;
  }
}

class VehículoTerrestre extends Vehículo {
  reparar() {
    console.log(`Un momento que me leo el manual de instrucciones de ${this.fabricante}`);
  }

  protected llevarAlTaller() {
    console.log('Llevando al taller...');
  }
}

class VehículoAéreo extends Vehículo {
  reparar() {
    this.llevarAlTaller();
  }

  protected llevarAlTaller() {
    console.log('Llevando al taller aéreo...');
  }
}

Por otro lado, cuando diseñamos jerarquías de clases, a menudo queremos que ciertas clases base no puedan ser instanciadas directamente porque representan conceptos abstractos. Por ejemplo, un Vehículo puede ser terrestre, aéreo o marítimo, pero no tiene sentido crear un objeto Vehículo genérico sin especificar su tipo. Para evitar que se creen instancias directas de estas clases base, TypeScript nos ofrece el modificador abstract.

Al declarar una clase como abstract, indicamos que esa clase solo sirve como plantilla para que otras clases la extiendan, pero no puede ser instanciada por sí misma. Esto mantiene la integridad del diseño y evita errores en tiempo de ejecución.

abstract class Vehículo {
  protected fabricante: string;

  constructor(fabricante: string) {
    this.fabricante = fabricante;
  }
}

class VehículoTerrestre extends Vehículo {
  // Implementaciones específicas
}

class VehículoAéreo extends Vehículo {
  // Implementaciones específicas
}

// Esto no es válido y dará error:
// const veh = new Vehículo('Genérico'); 

// En cambio, sí podemos hacer:
const coche = new VehículoTerrestre('Toyota');
const avión = new VehículoAéreo('Boeing');

Además, al trabajar con instancias y tipado, podemos declarar variables con el tipo de la clase base y asignarles objetos de clases derivadas. Esto es útil para funciones que operan sobre vehículos en general, sin importar su tipo específico. Por ejemplo, si tenemos una función que recibe un parámetro de tipo Vehículo, podemos pasarle un VehículoAéreo o un VehículoTerrestre sin problemas. Sin embargo, desde esa función solo tendremos acceso a las propiedades y métodos definidos en la clase base, lo que ayuda a encapsular detalles específicos y mantener un diseño limpio.

function mostrarFabricante(vehículo: Vehículo) {
  // Solo podemos acceder a propiedades y métodos de Vehículo
  console.log(`Fabricante: ${vehículo.fabricante}`);
}

const avión = new VehículoAéreo('Airbus');
mostrarFabricante(avión);

Este enfoque de encapsulación y control de acceso es clave para diseñar sistemas orientados a objetos bien estructurados, donde cada clase tiene responsabilidades claras y se minimizan los riesgos de modificar estados internos de manera indebida. Así, protected y abstract son herramientas esenciales para manejar la herencia y la seguridad en nuestras clases de TypeScript.

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