Clases abstractas en TypeScript

En TypeScript, las clases pueden heredar de otras para compartir métodos y propiedades. Además, TypeScript tiene soporte para clases abstractas. Quiero mostrarte por qué y cuándo conviene utilizarlas.
Por ejemplo, imagina que tenemos una clase base Subscription y creamos una nueva subclase FrenchSubscription con su propia implementación del método sign.
class Subscription {
url: string = "";
constructor(public name: string) {
this.name = name;
}
sign(): string {
return "Signup!!";
}
}
class FrenchSubscription extends Subscription {
url: string = "http://www.typescript.fr";
sign(): string {
return `Bonjour ${this.name} please go to ${this.url}`;
}
}
Luego, creamos una clase SubscriptionManager para procesar una lista de suscripciones y usar el método sign para mostrar un mensaje.
Este gestor de suscripciones deberá soportar futuras clases hijas de Subscription, como por ejemplo la clase EnglishSubscription.
SubscriptionManager tiene un método process para iterar sobre cada suscripción y llamar al método sign para mostrar el mensaje:
class SubscriptionManager {
static subscriptions: Array<Subscription> = [];
static process() {
this.subscriptions.forEach((p) => {
let message = p.sign();
console.log(message)
})
}
}
SubscriptionManager ya está listo para funcionar. Ahora agregamos una instancia de Subscription y la procesamos:
let france = new FrenchSubscription('Paris');
SubscriptionManager.subscriptions.push(france);
SubscriptionManager.process();[nodemon] starting `node Student.js`
Bonjour Paris please go to http://www.typescript.fr.Nodemon ejecuta la aplicación y todo funciona perfecto a primera vista. Podríamos pensar que ya está listo para pasar a producción, pero el código tiene una debilidad importante.
Imagina que un nuevo desarrollador se une al equipo y necesita crear una clase para suscripciones en inglés que herede de la clase base Subscription.
Crea EnglishSubscription y define el método sign. Como en el editor todo se ve bien y el compilador no se queja, sube sus cambios:
class EnglishSubscription extends Subscription {
url: string = "http://www.typescript.us";
sign() {
return false;
}
}El servicio comienza a usar la clase EnglishSubscription, la cual es un objeto de tipo Subscription que contiene el método sign:
let france = new FrenchSubscription('Paris');
let ny = new EnglishSubscription('New York');
SubscriptionManager.subscriptions.push(france);
SubscriptionManager.subscriptions.push(ny)
SubscriptionManager.process();¡Problema! El resultado no es lo que esperábamos:
Bonjour Paris please go to http://www.typescript.fr
falseAquí tenemos un error: el método existe, pero no cumple con el contrato inicial. El desarrollador no tenía cómo saber cuál era el contrato requerido por SubscriptionManager.
En muchos casos necesitamos asegurarnos de que quien programe cumpla con el objetivo de la clase base, garantizando que cada clase hija implemente todos los miembros requeridos con sus campos, métodos y firmas de tipos.
Aquí es donde entra en acción la clase abstracta. Al usar la palabra clave abstract antes del nombre de la clase y de los métodos, nuestra clase común se convierte en una clase abstracta, y cada clase hija debe implementar obligatoriamente el método con la firma esperada.
Tip: Los métodos abstractos no tienen una implementación.
abstract class Subscription {
url: string = "";
constructor(public name: string) {
this.name = name;
}
abstract sign(): string { }
}Ahora las clases hijas de Subscription deben implementar el método sign con la firma correcta. De lo contrario, el editor y el compilador marcarán un error:
Property 'sign' in type 'EnglishSubscription' is not assignable to the exact property in base type 'Subscription'.
Type '() => boolean' is not assignable to type '() => string'. Type 'boolean' is not assignable to type 'string'.Al usar abstract, los desarrolladores entienden claramente cuál es el objetivo de la clase base Subscription y de sus métodos. Además, nos aseguramos de que cada clase derivada implemente los métodos según lo previsto, con el tipo de retorno esperado pero con un comportamiento particular. De esta manera, nuestro código queda seguro y flexible.
Espero que este artículo te sirva de ayuda para entender las clases abstractas en TypeScript. Si te gustó este post, compártelo.
Photo by Karsten Winegeart on Unsplash
Modern frontend development — Angular, React, TypeScript, accessibility, and performance.
Artículos Relacionados
Diferencia entre 'extends' e 'implements' en TypeScript
Hoy un amigo me preguntó sobre la diferencia entre extends e implements. class Media { format: string; } class Video extends Media {} class Image implements Media {} La respuesta rápida para él fue: con extends la clase obtiene todos los métodos y propiedades del padre, mientras que con implements la clase debe implementarlos.
3 formas de transformar tipos en TypeScript
Cuando usamos tipos o interfaces en TypeScript, el compilador exige que el objeto coincida con ellos para evitar errores en tiempo de ejecución por propiedades faltantes. A veces queremos la flexibilidad de crear un objeto sin romper el contrato con la interfaz.
Cómo comprobar tipos en TypeScript (Guía completa)
Aprende cómo comprobar y validar tipos en TypeScript en tiempo de compilación y en tiempo de ejecución usando typeof, instanceof, el operador in, uniones discriminadas, type guards personalizados y Zod o Valibot.
Interfaces en TypeScript con un ejemplo
Las interfaces son una excelente funcionalidad de TypeScript para escribir código estructurado y explícito. Te permiten describir contratos con propiedades y métodos que tus objetos y clases deben cumplir.
Cuándo usar Arrays, Tuplas, Maps y Sets en TypeScript con ejemplos
Hace unos días, un amigo me preguntó cómo evitar claves duplicadas en un array. Le expliqué que existen otras colecciones para trabajar y que cada una se adapta a una situación específica. Como me encanta la NBA, busqué una forma sencilla de explicar cada estructura de datos.
Compartir este artículo
Si esta guía te resultó útil, compártela con tu equipo o con otros desarrolladores.
Software real. Lecciones reales.
Comparto las lecciones que aprendí por las malas, para que puedas evitarlas o estar preparado cuando ocurran.
Únete a 13,800+ desarrolladores y lectores.
Cero spam. Cancela tu suscripción en cualquier momento.