Skip to content

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

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 abstracta

Los 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!" ✅

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;
}
}

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().

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ísticaabstract classinterface
Instanciable
Métodos con implementación
Propiedades con valores
Métodos abstractosN/A
Constructor
Modificadores (private/protected)
Implementación múltiple❌ (solo 1 extends)✅ (varios implements)
// Interface: solo declaración
interface ILogger {
log(mensaje: string): void;
error(mensaje: string): void;
}
// Abstract class: puede tener implementación parcial
abstract 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ública
interface IRepositorio<T> {
obtenerPorId(id: string): T | null;
guardar(item: T): void;
eliminar(id: string): void;
}
// Clase abstracta con lógica base
abstract 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 concreta
class 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.


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;
}
}

Usa interface cuando…Usa abstract class cuando…
Solo necesitas definir la formaNecesitas compartir implementación
Trabajas con objetos literalesTrabajas con clases jerárquicas
Usas tipos de objetoNecesitas constructores
Quieres implementación múltipleNecesitas propiedades protected/private
Defines contratos para bibliotecasCreases 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.