Herencia, composición y polimorfismo
Herencia modela sustitución, composición arma comportamiento con colaboradores y polimorfismo permite usar implementaciones distintas detrás de un mismo contrato.
Para este tema conviene tener claro:Clases, objetos y estado
Reutilizar código no alcanza para justificar herencia. La pregunta es más exigente: ¿cada instancia de la subclase puede reemplazar a la clase base sin sorprender a quien usa el contrato?
Heredar es prometer sustitución
La herencia crea una relación de subtipo. Una implementación derivada hereda una interfaz y debe respetar sus expectativas: aceptar al menos las mismas entradas y conservar las garantías de salida.
Si una subclase necesita prohibir operaciones normales de la base o reinterpretar su significado, la jerarquía está modelando parecido de datos, no sustitución real.
Una función vieja del sistema recibe un rectángulo, le pone ancho 5 y alto 4, y espera área 20.
Antes de seguir, predecí
Componer es tener colaboradores
Composición significa construir un objeto con colaboradores. Un servicio de reportes puede recibir un repositorio y un renderer sin ser una subclase de ninguno. Delega tareas mediante contratos pequeños.
Esto permite reemplazar piezas independientemente y evita acoplar el comportamiento a una jerarquía rígida. La relación es “tiene un” o “usa un”, no “es un”.
La misma operación, implementaciones distintas
Polimorfismo es enviar la misma operación a implementaciones distintas:
type NotificationChannel = {
send(recipient: string, message: string): Promise<void>;
};
const notify = (
channel: NotificationChannel,
recipient: string,
message: string,
): Promise<void> => channel.send(recipient, message);Correo y mensajería pueden cumplir ese contrato sin compartir una clase base concreta.
Escena 1 — Una sola línea, tres métodos distintos
paso a paso
Cargando la escena…
Cuándo cada una
Usá herencia cuando existe una relación estable de sustitución y la base fue diseñada para extenderse. Usá composición cuando querés combinar capacidades, cambiar políticas o aislar infraestructura.
El polimorfismo no exige herencia: interfaces, funciones y tipos suma también permiten variar comportamiento. Elegí el mecanismo más chico que preserve el contrato.
La misma funcionalidad, de tres formas
// 1. Herencia: la subclase promete lo mismo que la base, y algo más
abstract class Notifier {
abstract send(message: string): Promise<void>;
async notifyAll(messages: readonly string[]): Promise<void> {
for (const message of messages) await this.send(message);
}
}
class EmailNotifier extends Notifier {
async send(message: string): Promise<void> { /* ... */ }
}
// 2. Composición: se recibe lo que hace falta, sin ser nada
type Sender = { send(message: string): Promise<void> };
const notifyAll = async (sender: Sender, messages: readonly string[]): Promise<void> => {
for (const message of messages) await sender.send(message);
};
// 3. Función: cuando la abstracción es una sola operación
const notifyWith = async (
send: (message: string) => Promise<void>,
messages: readonly string[],
): Promise<void> => {
for (const message of messages) await send(message);
};Las tres resuelven lo mismo y se prueban distinto. Para la primera hay que crear una subclase de prueba; para la segunda, un objeto con un método; para la tercera, una función. La dificultad de escribir el test es, otra vez, la mejor señal sobre el diseño.
La tercera es la que más se subestima: cuando la abstracción tiene una sola operación, una interfaz y una clase son un envoltorio alrededor de una función.
La regla de Liskov, en la práctica
| Una subclase no puede... | Ejemplo | Qué hacer |
|---|---|---|
| pedir más que la base | la base acepta cualquier entero, la subclase sólo positivos | no heredar: el contrato no se cumple |
| prometer menos | la base devuelve una lista ordenada, la subclase no | lo mismo |
| lanzar errores nuevos | la subclase falla donde la base funcionaba | devolver un resultado explícito en la base |
| prohibir una operación | una pila de sólo lectura que hereda de pila | partir la interfaz |
| cambiar el significado | el cuadrado que hereda del rectángulo | composición |
Cierre
Herencia define qué puede sustituir a qué; composición define quién colabora con quién; polimorfismo permite depender del contrato y no de la implementación. Separar esos conceptos evita jerarquías creadas sólo para reutilizar unas líneas.
Autoevaluación
¿Lo entendiste?
Práctica