CAP 09 · LEC 07·Programación orientada a objetos

Clases abstractas: contratos para subclases

Las clases abstractas definen una estructura base que no puede instanciarse directamente. Obligan a las subclases a implementar ciertos métodos, garantizando un contrato común.

● AVANZADO9 min lectura4 ejerciciospor Fernando Herrera · actualizado mayo de 2026
¿Encontraste un error o algo que mejorar?Editá esta lección en GitHub →

¿Qué es una clase abstracta?

Una clase abstracta es un molde incompleto — define la estructura y parte del comportamiento, pero deja ciertos métodos sin implementar para que las subclases los completen. No se puede instanciar directamente:

abstract class Figura { // Esta clase existe solo para ser extendida } // ❌ Error: Cannot create an instance of an abstract class // const f = new Figura(); class Circulo extends Figura {} // ✅ Instanciar la subclase concreta sí está permitido const c = new Circulo(); console.log(c instanceof Figura); // true — sigue siendo una Figura
Salidatrue
¿Para qué sirve?

Las clases abstractas modelan conceptos que no tienen sentido solos. Un "Animal" genérico no tiene sonido propio, pero Perro y Gato sí. La clase abstracta garantiza que toda subclase implemente lo que le falta.

abstract class en TypeScript

Una clase abstracta puede tener propiedades, métodos concretos y métodos abstractos mezclados:

abstract class Vehiculo { // Propiedades concretas — disponibles en todas las subclases readonly marca: string; readonly modelo: string; protected velocidadActual: number = 0; constructor(marca: string, modelo: string) { this.marca = marca; this.modelo = modelo; } // Método concreto — lógica compartida acelerar(incremento: number): void { this.velocidadActual += incremento; console.log(`${this.marca} ${this.modelo}: ${this.velocidadActual} km/h`); } // Método abstracto — cada subclase debe implementarlo abstract combustible(): string; abstract autonomiaKm(): number; } class AutoElectrico extends Vehiculo { private bateriaKwh: number; constructor(marca: string, modelo: string, bateriaKwh: number) { super(marca, modelo); this.bateriaKwh = bateriaKwh; } combustible(): string { return "Electricidad"; } autonomiaKm(): number { return this.bateriaKwh * 6; // ~6 km por kWh } } const tesla = new AutoElectrico("Tesla", "Model 3", 75); tesla.acelerar(60); console.log(tesla.combustible()); // "Electricidad" console.log(tesla.autonomiaKm()); // 450
SalidaTesla Model 3: 60 km/h Electricidad 450

Métodos abstractos: firma sin implementación

Los métodos abstractos declaran la firma (nombre, parámetros, tipo de retorno) pero no tienen cuerpo. La subclase está obligada a implementarlos:

abstract class Reporte { private titulo: string; private fecha: Date; constructor(titulo: string) { this.titulo = titulo; this.fecha = new Date(); } // Métodos abstractos: cada tipo de reporte define su contenido abstract generarCabecera(): string; abstract generarCuerpo(): string; abstract generarPie(): string; // Método concreto que usa los abstractos (Template Method pattern) generar(): string { return [ this.generarCabecera(), this.generarCuerpo(), this.generarPie(), `Generado: ${this.fecha.toLocaleDateString()}`, ].join("\n"); } } class ReportePDF extends Reporte { private datos: string[]; constructor(titulo: string, datos: string[]) { super(titulo); this.datos = datos; } generarCabecera(): string { return "=== REPORTE PDF ==="; } generarCuerpo(): string { return this.datos.map((d, i) => `${i + 1}. ${d}`).join("\n"); } generarPie(): string { return `Total de registros: ${this.datos.length}`; } } const reporte = new ReportePDF("Ventas Q1", ["Laptop: $1200", "Mouse: $25", "Teclado: $80"]); console.log(reporte.generar());
Salida=== REPORTE PDF === 1. Laptop: $1200 2. Mouse: $25 3. Teclado: $80 Total de registros: 3 Generado: 02/05/2026
Olvidar un método abstracto es un error

Si tu subclase concreta no implementa todos los métodos abstractos del padre, TypeScript lanzará un error en compilación: Non-abstract class 'X' does not implement inherited abstract member 'y'. Esto es una garantía valiosa.

Métodos concretos en abstractas

Las clases abstractas pueden tener métodos completamente implementados que las subclases heredan y pueden usar (o hacer override):

abstract class Autenticador { // Método concreto: flujo fijo de autenticación async autenticar(usuario: string, contrasena: string): Promise<boolean> { // 1. Paso concreto: log de intento this.registrarIntento(usuario); // 2. Paso abstracto: cada subclase valida a su manera const esValido = await this.validarCredenciales(usuario, contrasena); // 3. Paso concreto: manejar resultado if (esValido) { this.registrarExito(usuario); } else { this.registrarFallo(usuario); } return esValido; } // Métodos abstractos: la subclase define el "cómo" protected abstract validarCredenciales(usuario: string, contrasena: string): Promise<boolean>; // Métodos concretos con comportamiento por defecto protected registrarIntento(usuario: string): void { console.log(`[AUTH] Intento de login: ${usuario}`); } protected registrarExito(usuario: string): void { console.log(`[AUTH] Login exitoso: ${usuario}`); } protected registrarFallo(usuario: string): void { console.log(`[AUTH] Login fallido: ${usuario}`); } } class AutenticadorLocal extends Autenticador { private usuarios = new Map([["admin", "1234"], ["ana", "pass"]]); protected async validarCredenciales(usuario: string, contrasena: string): Promise<boolean> { return this.usuarios.get(usuario) === contrasena; } } const auth = new AutenticadorLocal(); await auth.autenticar("admin", "1234"); // exitoso await auth.autenticar("hacker", "0000"); // fallido
Salida[AUTH] Intento de login: admin [AUTH] Login exitoso: admin [AUTH] Intento de login: hacker [AUTH] Login fallido: hacker

Abstractas vs interfaces: cuándo usar cada una

Ambas definen contratos, pero tienen capacidades distintas. La elección correcta depende del contexto:

CaracterísticaClase abstractaInterface
Implementación de métodosSí — puede tener códigoNo — solo firmas
Estado (propiedades con valor)No (solo tipos)
ConstructorNo
Herencia múltipleNo — solo una clase padreSí — múltiples interfaces
Modificadores de accesoSí (private, protected)No — todo es público
Cuando usarJerarquía con código compartidoContrato puro, sin lógica compartida
// ✅ Usa interface cuando defines solo la forma del objeto interface Serializable { toJSON(): object; fromJSON(data: object): void; } // ✅ Usa abstract class cuando compartes lógica entre subclases abstract class BaseDatos { private conectado = false; async conectar(): Promise<void> { await this.establecerConexion(); // abstracto this.conectado = true; console.log("Conectado"); } protected abstract establecerConexion(): Promise<void>; abstract consultar(sql: string): Promise<unknown[]>; } // ✅ Puedes combinar ambas: abstracta que implementa interfaces abstract class Repositorio implements Serializable { abstract toJSON(): object; abstract fromJSON(data: object): void; abstract guardar(): Promise<void>; } console.log("TypeScript te da todas las herramientas");
SalidaTypeScript te da todas las herramientas
  1. Usa abstract class cuando necesitas compartir código entre clases relacionadas
  2. Usa interface cuando solo necesitas definir la forma de un objeto o un contrato sin lógica
  3. Una clase puede implementar múltiples interfaces pero solo extender una clase abstracta
  4. Los métodos abstractos obligan a las subclases a completar el contrato — úsalos en lugar de lanzar errores manualmente

Practica