patron-flyweight

Patrón Flyweight: comparte estado y ahorra memoria

  • 5 min

El Flyweight es un patrón que comparte el estado común entre muchos objetos y mantiene fuera los datos propios de cada contexto. Su objetivo es reducir memoria cuando existen cantidades muy grandes de elementos parecidos.

Imagina un simulador que renderiza un millón de árboles. Si cada Arbol duplica su malla y sus texturas, el consumo se dispara:

  • Modelo 3D + Texturas = 5 MB (por decir algo).
  • 1 millón de árboles x 5 MB = 5.000 GB de RAM.

El mapa agotaría la memoria mucho antes de cargarse.

Sin embargo, la mayoría comparte los mismos recursos. Todos los pinos pueden usar una misma malla y textura; entre instancias solo cambian la posición y quizá la escala.

La idea es guardar una vez los datos comunes y compartirlos.

Estado Intrínseco vs Extrínseco

Para aplicar este patrón, tenemos que dividir los datos de nuestros objetos en dos categorías:

Estado Intrínseco (Shared): Son los datos que NO cambian entre instancias. Son constantes para un tipo de objeto.

  • Ejemplo: El modelo 3D del pino, la textura, el nombre de la especie.
  • Dónde va: En el objeto Flyweight.

Estado Extrínseco (Unique): Son los datos que SÍ cambian y hacen único a cada objeto.

  • Ejemplo: La coordenada (X, Y), la salud actual del árbol, el tamaño.
  • Dónde va: Se pasa por parámetro desde fuera (el cliente los gestiona).

Implementación en C#

Vamos a plantar ese bosque de un millón de árboles sin fundir la RAM.

El Flyweight (Lo que se comparte)

Esta clase contiene los datos pesados. Fíjate que no tiene coordenadas. Las coordenadas se las pasan al método Dibujar.

// Flyweight
public class TipoArbol
{
    private string _nombre;
    private string _color;
    private string _modelo3D; // Imagina que esto ocupa muchos MB

    public TipoArbol(string nombre, string color, string modelo)
    {
        _nombre = nombre;
        _color = color;
        _modelo3D = modelo;
    }

    // Operación que usa el estado extrínseco (x, y)
    public void Dibujar(int x, int y)
    {
        Console.WriteLine($"🌳 Dibujando {_nombre} ({_color}) en coordenadas [{x}, {y}] usando modelo {_modelo3D}");
    }
}
Copied!

La Fábrica (El gestor de caché)

Esta clase asegura que no creemos duplicados del TipoArbol.

public class FabricaArboles
{
    // Diccionario para caché: "NombreTipo" -> Objeto
    private static Dictionary<string, TipoArbol> _tipos = new Dictionary<string, TipoArbol>();

    public static TipoArbol GetTipoArbol(string nombre, string color, string modelo)
    {
        // Si no existe, lo creamos y guardamos
        if (!_tipos.ContainsKey(nombre))
        {
            Console.WriteLine($"--- FABRICA: Creando nuevo tipo de árbol: {nombre} ---");
            _tipos[nombre] = new TipoArbol(nombre, color, modelo);
        }
        
        // Devolvemos la instancia compartida
        return _tipos[nombre];
    }
}
Copied!

El Árbol (El Contexto)

Este es el objeto que realmente instanciamos muchas veces. Pero es muy ligero. Solo tiene coordenadas y una referencia al tipo.

public class Arbol
{
    private int _x;
    private int _y;
    private TipoArbol _tipo; // Referencia al Flyweight

    public Arbol(int x, int y, TipoArbol tipo)
    {
        _x = x;
        _y = y;
        _tipo = tipo;
    }

    public void Dibujar()
    {
        // Delegamos en el Flyweight, pasándole nuestro estado único
        _tipo.Dibujar(_x, _y);
    }
}
Copied!

La Prueba de Fuego

class Program
{
    static void Main(string[] args)
    {
        var bosque = new List<Arbol>();

        // Vamos a plantar 10.000 árboles
        Console.WriteLine("Plantando bosque...");

        for (int i = 0; i < 10000; i++)
        {
            // Simulamos que hay solo 2 tipos de árboles en el bosque
            string nombreTipo = (i % 2 == 0) ? "Pino" : "Roble";
            string color = (i % 2 == 0) ? "Verde Oscuro" : "Verde Claro";
            string modelo = (i % 2 == 0) ? "Pino.obj" : "Roble.obj";

            // 1. Pedimos el tipo a la fábrica (solo creará 2 objetos en total)
            TipoArbol tipo = FabricaArboles.GetTipoArbol(nombreTipo, color, modelo);

            // 2. Creamos el árbol ligero con sus coordenadas únicas
            Arbol arbol = new Arbol(i, i, tipo);
            bosque.Add(arbol);
        }

        Console.WriteLine($"\n¡Bosque plantado con {bosque.Count} árboles!");
        
        // Dibujamos solo los primeros 5 para ver que funciona
        for(int i=0; i<5; i++) bosque[i].Dibujar();
    }
}
Copied!

Salida:

Plantando bosque...
--- FABRICA: Creando nuevo tipo de árbol: Pino ---
--- FABRICA: Creando nuevo tipo de árbol: Roble ---

¡Bosque plantado con 10000 árboles!
🌳 Dibujando Pino (Verde Oscuro) en coordenadas [0, 0] usando modelo Pino.obj
🌳 Dibujando Roble (Verde Claro) en coordenadas [1, 1] usando modelo Roble.obj
...
Copied!

Resultado: Hemos creado 10.000 objetos Arbol (que ocupan muy pocos bytes), pero solo hemos creado 2 objetos TipoArbol pesados en memoria. El ahorro es brutal.

Flyweight en el Mundo Real (C++ y C#)

Este patrón es el pan de cada día en los motores de juegos.

  • Unity / Unreal Engine: Cuando pones 500 cajas en una escena, el motor usa una técnica llamada GPU Instancing. Envía la malla de la caja una sola vez a la tarjeta gráfica y luego envía una lista de 500 posiciones. Eso es Flyweight a nivel de hardware.
  • String interning en .NET y Java: los literales iguales suelen compartir una instancia internada. Las cadenas creadas en tiempo de ejecución no se internan automáticamente en todos los casos, aunque pueden añadirse explícitamente al pool.

Ventajas y Desventajas

VentajasDesventajas
Ahorro masivo de RAM: Fundamental para sistemas con muchos objetos repetidos.Complejidad de CPU: A veces, calcular el estado extrínseco o buscar en el caché consume tiempo de procesador. Cambiamos RAM por CPU.
Rendimiento: Al tener menos objetos pesados, el Garbage Collector trabaja menos.Código más complejo: Separar el estado en dos partes hace que el código sea más difícil de leer.

El Flyweight no es un patrón que uses en un CRUD web típico. Pero el día que tengáis que procesar un documento de texto gigante (donde cada letra es un objeto) o renderizar partículas en un juego, este patrón será la diferencia entre que vuestra aplicación vuele o se estrelle.