Flyweight
Un patrón estructural para tener más objetos en memoria, compartiendo entre ellos el estado que se repite.
Flyweight es un patrón estructural que permite mantener más objetos en memoria compartiendo la parte de su estado que es común entre muchos de ellos.
El problema
La página de categoría “Trekking” de AndesShop muestra miles de productos en simultáneo, y cada tarjeta de producto puede llevar una o más etiquetas visuales — “Nuevo”, “Oferta”, “Últimas unidades”. Si cada tarjeta crea su propio objeto Badge con el ícono, el color y el texto de la etiqueta, terminás con miles de objetos que contienen exactamente los mismos tres datos repetidos (“Oferta” siempre es el mismo ícono, el mismo color naranja, el mismo texto) — la única diferencia real entre tarjetas es a qué producto pertenece cada una.
La solución
Flyweight separa el estado de un objeto en dos partes: el estado intrínseco, que es idéntico entre muchas instancias y se puede compartir (el ícono, color y texto de “Oferta”), y el estado extrínseco, que es propio de cada uso puntual (a qué producto pertenece, en qué posición se muestra) y que el código cliente pasa como parámetro en el momento de usarlo. Una fábrica se encarga de devolver siempre el mismo objeto compartido para un mismo estado intrínseco, en vez de crear uno nuevo cada vez.
classDiagram
class BadgeStyle {
<<flyweight · estado intrínseco compartido>>
-icon String
-color String
-label String
+render(String sku, int x, int y) String
}
class BadgeFactory {
-cache Map~String, BadgeStyle~
+getBadge(String type) BadgeStyle
}
class ProductBadge {
<<contexto · estado extrínseco>>
-sku String
-x int
-y int
-style BadgeStyle
+draw() String
}
BadgeFactory o-- "0..*" BadgeStyle : cache de instancias compartidas
ProductBadge --> "1" BadgeStyle : style compartido
ProductBadge ..> BadgeFactory : pide el flyweightEjemplo en Java
// Flyweight: contiene solo el estado intrínseco, compartido entre muchos productos
class BadgeStyle {
private final String icon;
private final String color;
private final String label;
public BadgeStyle(String icon, String color, String label) {
this.icon = icon;
this.color = color;
this.label = label;
}
// El estado extrínseco (productSku) llega como parámetro, no se guarda acá
public String render(String productSku) {
return icon + " " + label + " (" + productSku + ")";
}
}
// Factory: garantiza una única instancia de BadgeStyle por tipo
class BadgeFactory {
private final Map<String, BadgeStyle> cache = new HashMap<>();
public BadgeStyle getBadge(String type) {
return cache.computeIfAbsent(type, t -> switch (t) {
case "OFFER" -> new BadgeStyle("🏷️", "orange", "Oferta");
case "NEW" -> new BadgeStyle("✨", "blue", "Nuevo");
default -> throw new IllegalArgumentException("Unknown badge type: " + t);
});
}
}
// Client code: miles de tarjetas de producto, un puñado de instancias de BadgeStyle
BadgeFactory factory = new BadgeFactory();
for (Product product : catalogPage) {
BadgeStyle badge = factory.getBadge("OFFER"); // misma instancia para todas las ofertas
System.out.println(badge.render(product.getSku()));
}
Cuándo usarlo
- Cuando tenés una cantidad muy grande de objetos que comparten buena parte de su estado, y eso está consumiendo memoria de forma notoria.
- Cuando ese estado compartido se puede separar limpiamente del estado que sí varía por instancia.
Cuándo evitarlo
Con una cantidad moderada de objetos, o cuando el estado compartible es chico frente al que varía, el ahorro de memoria no compensa la complejidad extra de separar estado intrínseco y extrínseco.
Ventajas y desventajas
| Ventajas | Desventajas |
|---|---|
| Reduce significativamente el uso de memoria cuando hay muchísimas instancias similares | El código gana complejidad al separar estado intrínseco y extrínseco |
| Centraliza el estado compartido en un solo lugar | El estado extrínseco tiene que recalcularse o pasarse cada vez, lo que puede costar tiempo de CPU a cambio de memoria |
