Saltar al contenido
Explorar conocimiento

concepto · fundamentos · 6 min de lectura

Composición

La composición construye comportamiento combinando objetos colaboradores, delegando responsabilidades en partes más pequeñas en vez de heredar todo desde una clase base.

Antes de leer esto, conviene conocer: Clase.

Alcance en breve

Cubre

  • object composition as building behavior from collaborating parts
  • delegation through fields, constructor injection, and small contracts at a conceptual level
  • practical trade-offs between composition and inheritance

No cubre

  • the Composite design pattern as a separate pattern
  • dependency injection frameworks and containers
  • functional composition and algebraic composition

Supone

  • The reader understands classes, objects, methods, and basic inheritance vocabulary.

Resumen

Composición es diseñar un objeto a partir de otros objetos que colaboran con él. En vez de decir "esta clase es un tipo de otra", composición dice "esta clase tiene o usa otro objeto para hacer una parte del trabajo".

Importa porque reduce acoplamiento. Una clase puede delegar una responsabilidad específica en un colaborador y cambiar ese colaborador sin reescribir su identidad completa. Por eso suele ser una alternativa más flexible que herencia cuando la relación real es "tiene un", "usa un" o "se comporta con".

Alcance y supuestos

Este paquete cubre composición de objetos a nivel práctico: campos que guardan colaboradores, delegación de comportamiento y contratos pequeños que permiten intercambiar partes. Usa TypeScript como ejemplo, pero el razonamiento aplica a lenguajes orientados a objetos y a diseños modulares en general.

No cubre el patrón Composite como patrón estructural específico, contenedores de dependency injection, composición funcional, álgebra, traits ni mixins. Asume que ya entiendes clases, objetos, métodos y la diferencia básica entre composición y herencia.

Modelo mental

Piensa en armar un computador. El computador no hereda de una placa madre, un disco y una fuente de poder. Tiene esas partes y coordina cómo trabajan juntas. Si cambias el disco por uno más rápido, el computador sigue siendo computador; solo reemplazaste un componente.

Esa es la idea central de composición: construir comportamiento conectando piezas. Cada pieza tiene una responsabilidad acotada, y el objeto principal delega en ellas cuando necesita esa capacidad.

La ventaja aparece cuando una variación no cambia la identidad del objeto. Un carrito no es un calculador de descuentos; tiene o usa uno. Una notificación no es un transportador SMTP; usa uno. Si modelas esas relaciones con herencia, la clase queda atada a una jerarquía rígida. Con composición, puedes reemplazar el colaborador correcto.

Uso práctico

Usa composición cuando una clase necesita una capacidad, estrategia o servicio que puede variar independientemente:

  • Cart puede usar un DiscountPolicy para calcular descuentos sin heredar de cada tipo de descuento.
  • ReportExporter puede usar un Formatter para producir HTML, PDF o CSV sin convertirse en una subclase por formato.
  • PdfReport extends ReportExporter puede crear una jerarquía frágil si el formato es solo una pieza intercambiable.
  • ❌ Componer demasiados objetos minúsculos sin una razón de variación real agrega indirección innecesaria.

Ejemplo trabajado: calcular descuentos con una política intercambiable

Supón que una tienda calcula descuentos de distintas formas. Si el carrito hereda de SeasonalDiscountCart, VipDiscountCart y luego CouponDiscountCart, la identidad del carrito se mezcla con reglas comerciales que cambian con frecuencia. La composición separa ambas cosas: el carrito guarda ítems y delega el descuento a una política.

interface DiscountPolicy {
  apply(subtotal: number): number;
}

class NoDiscount implements DiscountPolicy {
  apply(subtotal: number) {
    return subtotal;
  }
}

class PercentageDiscount implements DiscountPolicy {
  constructor(private percent: number) {}

  apply(subtotal: number) {
    return subtotal * (1 - this.percent / 100);
  }
}

class Cart {
  private items: number[] = [];

  constructor(private discountPolicy: DiscountPolicy) {}

  addItem(price: number) {
    this.items.push(price);
  }

  total() {
    const subtotal = this.items.reduce((sum, price) => sum + price, 0);
    return this.discountPolicy.apply(subtotal);
  }
}

Cart no sabe qué descuento concreto está usando. Solo sabe que tiene un colaborador que cumple DiscountPolicy. La regla de descuento puede cambiar sin crear otra subclase de carrito.

const cart = new Cart(new PercentageDiscount(10));
cart.addItem(100);
cart.addItem(50);

console.log(cart.total()); // 135

El carrito suma los ítems y delega el ajuste final. Si mañana no hay descuento, puedes construir new Cart(new NoDiscount()). Si aparece un descuento por cupón, agregas otra implementación de DiscountPolicy sin cambiar el código que administra ítems.

La frontera está en la responsabilidad. Composición funciona bien cuando el colaborador representa una parte reemplazable del comportamiento. No significa partir todo en objetos por reflejo. Si una regla es estable, pequeña y pertenece completamente al objeto principal, dejarla dentro puede ser más simple.

Antes de elegir composición, revisa cuatro preguntas:

  1. ¿La relación real es "tiene un" o "usa un" en vez de "es un tipo de"?
  2. ¿Esa parte puede cambiar sin cambiar la identidad del objeto principal?
  3. ¿El colaborador tiene una responsabilidad clara y nombrable?
  4. ¿La flexibilidad ganada compensa la indirección agregada?

Si las respuestas son sí, composición suele mantener el diseño más flexible. Si la subclase realmente conserva el contrato de la superclase y solo lo especializa, herencia puede ser más directa.

Evidencia

Conexiones

Requisitos

Requiere

Relacionados y alternativas

Relacionado

Contrasta con

Ver grafo local

Fuentes citadas