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.