Lección 96: Clases Abstractas, implements vs extends
🎯 Objetivo: Entender las clases abstractas, la diferencia entre implements y extends, y cuándo usar cada uno.
🧠 Concepto
1. Clases abstractas
Section titled “1. Clases abstractas”Una clase abstracta es una clase que no puede ser instanciada directamente. Sirve como plantilla para otras clases.
💻 Ejemplo
abstract class Animal { constructor(public nombre: string) {}
// Método concreto (con implementación) respirar(): void { console.log(`${this.nombre} está respirando`); }
// Método abstracto (sin implementación) abstract hacerSonido(): void;}
// const animal = new Animal("genérico"); // ❌ Error: clase abstractaLos métodos abstractos obligan a las subclases a implementarlos:
class Perro extends Animal { constructor(nombre: string) { super(nombre); }
hacerSonido(): void { console.log("¡Guau guau!"); }}
class Gato extends Animal { constructor(nombre: string) { super(nombre); }
hacerSonido(): void { console.log("¡Miau!"); }}
const perro = new Perro("Firulais");perro.respirar(); // "Firulais está respirando" ✅perro.hacerSonido(); // "¡Guau guau!" ✅Propiedades abstractas
Section titled “Propiedades abstractas”También puedes declarar propiedades como abstractas:
abstract class Figura { abstract color: string;
abstract calcularArea(): number;
descripcion(): string { return `Figura de color ${this.color} con área ${this.calcularArea()}`; }}
class Circulo extends Figura { color: string; constructor(public radio: number, color: string) { super(); this.color = color; }
calcularArea(): number { return Math.PI * this.radio ** 2; }}2. extends vs implements
Section titled “2. extends vs implements”extends — Herencia de implementación
Section titled “extends — Herencia de implementación”class Vehiculo { constructor(public marca: string) {}
encender(): void { console.log("Vehículo encendido"); }}
class Coche extends Vehiculo { constructor(marca: string, public puertas: number) { super(marca); }
encender(): void { console.log("Coche encendido — revisa espejos"); }}
const coche = new Coche("Toyota", 4);coche.encender(); // "Coche encendido — revisa espejos"console.log(coche.marca); // "Toyota" ✅ heredado📝 extends: hereda implementación (métodos concretos, propiedades) y requiere super().
implements — Verificación de interfaz
Section titled “implements — Verificación de interfaz”interface IVolador { volar(): void; aterrizar(): void;}
interface INadador { nadar(): void;}
class Avion implements IVolador { volar(): void { console.log("Volando a 10,000m"); }
aterrizar(): void { console.log("Aterrizando"); }}
class Pato implements IVolador, INadador { volar(): void { console.log("Pato volando"); }
aterrizar(): void { console.log("Pato aterrizando en el agua"); }
nadar(): void { console.log("Pato nadando"); }}📝 implements: verifica que la clase tenga la forma (shape) de la interfaz. No hereda implementación.
3. Diferencia clave: implementación vs declaración
Section titled “3. Diferencia clave: implementación vs declaración”| Característica | abstract class | interface |
|---|---|---|
| Instanciable | ❌ | ❌ |
| Métodos con implementación | ✅ | ❌ |
| Propiedades con valores | ✅ | ❌ |
| Métodos abstractos | ✅ | N/A |
| Constructor | ✅ | ❌ |
| Modificadores (private/protected) | ✅ | ❌ |
| Implementación múltiple | ❌ (solo 1 extends) | ✅ (varios implements) |
// Interface: solo declaracióninterface ILogger { log(mensaje: string): void; error(mensaje: string): void;}
// Abstract class: puede tener implementación parcialabstract class LoggerBase implements ILogger { abstract log(mensaje: string): void;
error(mensaje: string): void { this.log(`[ERROR] ${mensaje}`); // ✅ implementación compartida }}
class LoggerConsola extends LoggerBase { log(mensaje: string): void { console.log(`[LOG] ${mensaje}`); }}4. Patrón: Abstract class + Interface combinados
Section titled “4. Patrón: Abstract class + Interface combinados”// Interfaz públicainterface IRepositorio<T> { obtenerPorId(id: string): T | null; guardar(item: T): void; eliminar(id: string): void;}
// Clase abstracta con lógica baseabstract class RepositorioBase<T> implements IRepositorio<T> { protected items: Map<string, T> = new Map();
abstract obtenerPorId(id: string): T | null;
guardar(item: T & { id: string }): void { this.items.set(item.id, item); }
eliminar(id: string): void { this.items.delete(id); }
contar(): number { return this.items.size; }}
// Implementación concretaclass RepositorioUsuario extends RepositorioBase<{ id: string; nombre: string; email: string;}> { obtenerPorId(id: string): { id: string; nombre: string; email: string } | null { return this.items.get(id) ?? null; }}🧠 Este patrón es común en aplicaciones reales: la interfaz define el contrato, la clase abstracta proporciona implementación base, y las clases concretas completan la funcionalidad.
5. Herencia múltiple no existe, pero…
Section titled “5. Herencia múltiple no existe, pero…”JavaScript/TypeScript no tiene herencia múltiple de clases. Sin embargo, puedes combinar extends + implements:
interface IExportable { exportar(): string;}
interface IImportable { importar(datos: string): void;}
class DocumentoBase { constructor(public titulo: string) {}}
class Documento extends DocumentoBase implements IExportable, IImportable { private contenido: string = "";
exportar(): string { return JSON.stringify({ titulo: this.titulo, contenido: this.contenido }); }
importar(datos: string): void { const parsed = JSON.parse(datos); this.titulo = parsed.titulo; this.contenido = parsed.contenido; }}6. Cuándo usar cada uno 🎯
Section titled “6. Cuándo usar cada uno 🎯”Usa interface cuando… | Usa abstract class cuando… |
|---|---|
| Solo necesitas definir la forma | Necesitas compartir implementación |
| Trabajas con objetos literales | Trabajas con clases jerárquicas |
| Usas tipos de objeto | Necesitas constructores |
| Quieres implementación múltiple | Necesitas propiedades protected/private |
| Defines contratos para bibliotecas | Creases frameworks o bases reutilizables |
📝 Resumen: Las clases abstractas son plantillas que pueden tener implementación parcial. implements verifica que una clase cumpla una interfaz (solo forma), mientras extends hereda implementación. La combinación de ambos permite diseños flexibles y reutilizables.