Skip to content
SP StackPractices
intermediate

Patrón Flyweight

Comparte objetos para soportar eficientemente grandes cantidades de objetos de grano fino. Un patrón estructural para optimización de memoria.

Temas: design

Patrón Flyweight

Visión General

El Patrón Flyweight es un patrón de diseño estructural que minimiza el uso de memoria compartiendo la mayor cantidad de datos posible entre objetos similares. En lugar de almacenar estado redundante en cada instancia, separas el estado intrínseco (compartido) del estado extrínseco (único por contexto) y reutilizas objetos flyweight a través de múltiples contextos.

Cuándo Usarlo

Usa el Patrón Flyweight cuando:

  • Tu aplicación usa un gran número de objetos que comparten estado común
  • Los costos de almacenamiento de objetos son altos debido a duplicación masiva
  • La mayor parte del estado de un objeto puede hacerse extrínseco (computado o pasado)
  • Necesitas soportar muchos objetos granulares sin agotar la memoria
  • Ejemplos: caracteres en un documento, baldosas en un mapa de juego, íconos en una UI

Solución

Python

class TreeType:
    _cache: dict = {}

    def __init__(self, species: str, color: str, texture: str):
        self.species = species
        self.color = color
        self.texture = texture

    @classmethod
    def get(cls, species: str, color: str, texture: str):
        key = (species, color, texture)
        if key not in cls._cache:
            cls._cache[key] = cls(species, color, texture)
        return cls._cache[key]

    def render(self, x: int, y: int):
        print(f"Renderizando {self.species} en ({x}, {y}) "
              f"con color={self.color}, textura={self.texture}")

class Tree:
    def __init__(self, x: int, y: int, tree_type: TreeType):
        self.x = x
        self.y = y
        self.tree_type = tree_type

    def render(self):
        self.tree_type.render(self.x, self.y)

# Uso: miles de árboles, solo unos pocos tipos compartidos
for i in range(1000):
    t = Tree(i, i, TreeType.get("Oak", "green", "bark.png"))
    t.render()

print(f"Tipos de árbol únicos: {len(TreeType._cache)}")  # 1, no 1000

JavaScript

class TreeType {
  static cache = new Map();

  constructor(species, color, texture) {
    this.species = species;
    this.color = color;
    this.texture = texture;
  }

  static get(species, color, texture) {
    const key = `${species}|${color}|${texture}`;
    if (!this.cache.has(key)) {
      this.cache.set(key, new TreeType(species, color, texture));
    }
    return this.cache.get(key);
  }

  render(x, y) {
    console.log(`Renderizando ${this.species} en (${x}, ${y}) color=${this.color}`);
  }
}

class Tree {
  constructor(x, y, treeType) {
    this.x = x;
    this.y = y;
    this.treeType = treeType;
  }

  render() {
    this.treeType.render(this.x, this.y);
  }
}

// Uso
for (let i = 0; i < 1000; i++) {
  const t = new Tree(i, i, TreeType.get("Oak", "green", "bark.png"));
  t.render();
}

console.log(`Tipos de árbol únicos: ${TreeType.cache.size}`); // 1, no 1000

Java

import java.util.HashMap;
import java.util.Map;

public class TreeType {
    private static final Map<String, TreeType> cache = new HashMap<>();

    private final String species;
    private final String color;
    private final String texture;

    private TreeType(String species, String color, String texture) {
        this.species = species;
        this.color = color;
        this.texture = texture;
    }

    public static TreeType get(String species, String color, String texture) {
        String key = species + "|" + color + "|" + texture;
        return cache.computeIfAbsent(key, k -> new TreeType(species, color, texture));
    }

    public void render(int x, int y) {
        System.out.println("Renderizando " + species + " en (" + x + ", " + y + ")");
    }
}

public class Tree {
    private final int x, y;
    private final TreeType type;

    public Tree(int x, int y, TreeType type) {
        this.x = x;
        this.y = y;
        this.type = type;
    }

    public void render() {
        type.render(x, y);
    }
}

// Uso
for (int i = 0; i < 1000; i++) {
    new Tree(i, i, TreeType.get("Oak", "green", "bark.png")).render();
}
System.out.println("Tipos de árbol únicos: " + TreeType.cache.size());

Explicación

El Patrón Flyweight separa el estado en dos categorías:

  • Estado intrínseco (species, color, texture): Compartido entre muchos objetos, almacenado dentro del flyweight
  • Estado extrínseco (x, y): Único para cada contexto, pasado cuando se usa el flyweight

La Fábrica Flyweight (TreeType.get()) gestiona un cache de instancias flyweight compartidas. En lugar de crear un nuevo objeto por cada árbol, recuperas (o creas) un tipo compartido y lo usas a través de muchas instancias de árbol.

Variantes

VarianteDescripciónCaso de Uso
Flyweight SimpleUn solo objeto compartido por estado intrínseco únicoGlifos de caracteres, íconos
Flyweight No CompartidoAlgunas instancias no se cacheanRaramente usado, pero permite flexibilidad
Flyweight CompuestoFlyweights compuestos de otros flyweightsElementos UI complejos
Internamiento de StringsCaracterística incorporada del lenguajeString.intern() de Java, internamiento de Python

Buenas Prácticas

  • Aplica solo cuando la presión de memoria sea real — la optimización prematura agrega complejidad
  • Haz los flyweights inmutables para prevenir corrupción de estado compartido
  • Usa referencias débiles para caches si los flyweights son grandes y pueden ser recolectados
  • Perfila antes y después para verificar que los ahorros de memoria justifiquen la complejidad
  • Considera la fábrica como un cache con políticas de evicción opcionales (LRU, TTL)

Errores Comunes

  • Usar flyweights cuando la división intrínseca/extrínseca no está clara, llevando a código frágil
  • Hacer flyweights mutables, causando corrupción de estado compartido entre contextos
  • Olvidar la seguridad de hilos en el cache de la fábrica cuando se accede concurrentemente
  • Sobre-ingeniería de la fábrica con lógica de evicción compleja para conjuntos pequeños
  • Almacenar estado extrínseco dentro del flyweight, derrotando el propósito

Preguntas Frecuentes

P: ¿Es Flyweight lo mismo que Singleton? R: No. Singleton fuerza exactamente una instancia de una clase. Flyweight crea una instancia por combinación única de estado intrínseco. Un singleton es un caso especial donde todo el estado es compartido.

P: ¿Cuándo no debería usar Flyweight? R: Evítalo cuando los objetos sean pocos, el estado sea mayoritariamente único, o los ahorros de memoria no justifiquen la complejidad añadida. Mide primero, optimiza después.

P: ¿Cómo se diferencia Flyweight del Pool de Objetos? R: El Pool de Objetos reutiliza objetos para evitar overhead de asignación. Flyweight comparte objetos para reducir el uso de memoria. Los objetos del pool son típicamente mutables y devueltos al pool; los flyweights se comparten simultáneamente entre contextos.