🚨 ¡Nueva review! 🔇 Los mejores cascos con ANC del mercado: los Sony WH-1000XM4 . ¡Échale un ojo! 👀

El Patrón de diseño Flyweight

¡Ahorra memoria compartiendo objetos de forma inteligente!

Escrito por domin el 10 de noviembre de 2025 · Actualizado el 15 de febrero de 2026

💾 El Patrón Flyweight: El gran ahorrador de memoria

¿Te ha pasado alguna vez que tu aplicación necesita crear miles o millones de objetos que son casi iguales y la RAM se va a la puta? Pues para eso existe el patrón de diseño Flyweight, un patrón estructural que va perfecto para ahorrar memoria compartiendo todo lo que se pueda entre objetos.

La idea es bastante de simple: si tienes 10.000 objetos y todos comparten los mismos datos pesados, ¿para qué copiar esos datos 10.000 veces? Creas una única instancia compartida y que todos la referencien. EA.

No dupliques lo que es igual. Guárdalo una vez y que todo el mundo tire de la misma copia.

Diagrama del Patrón Flyweight mostrando múltiples clientes compartiendo un único objeto Flyweight común, y cada uno con su propio estado externo único.

💾 1. Estado intrínseco vs extrínseco

Esto es lo más importante del patrón, así que pay attention please:

Estado Intrínseco (compartido)

Lo que es igual para todos los objetos del mismo tipo. No cambia. Se mete dentro del Flyweight y se comparte. Ejemplo: la textura de un árbol, el sprite de un enemigo, la fuente de un carácter.

Estado Extrínseco (único)

Lo que es diferente en cada instancia. Cambia. Se guarda fuera del Flyweight y se le pasa cuando toca. Ejemplo: la posición X,Y de un árbol, la vida de un enemigo, el color de un carácter.

Todo el patrón se basa en esta separación. Lo que se repite va dentro del Flyweight (una sola copia para todos). Lo que varía se queda fuera y se pasa como parámetro cuando hace falta.

¿Qué es el estado intrínseco en un Flyweight?


🚗 2. La fábrica de coches

Imagina una fábrica que saca miles de coches:

¿Cuál es el principal objetivo del Patrón Flyweight?


🛠️ 3. Los roles del patrón

Flyweight (Objeto compartido)

Guarda el estado intrínseco (lo común e inmutable). Cuando tiene que hacer algo, recibe el estado extrínseco como parámetro.

Factoría del Flyweight

El cerebro del chiringuito. Si ya existe un Flyweight con esa clave, te lo devuelve. Si no, lo crea y lo guarda. Básicamente es una caché de objetos.

Cliente

Guarda el estado extrínseco (lo que es único de cada uso). Cuando necesita operar, le pasa ese estado al Flyweight.

El cliente nunca crea Flyweights directamente. Siempre pasa por la Factory. Si te saltas la Factory y haces new, te cargas todo el beneficio del patrón.

¿Cuál es el rol de la Factoría del Flyweight?


🧑‍💻 4. Ejemplo práctico en PHP: Árboles en un juego

Imagina un RPG con miles de árboles. Todos los Robles tienen la misma textura (20MB) y el mismo modelo 3D. Lo que cambia es dónde está plantado cada uno.

<?php

// 1. Interfaz del Flyweight
interface TipoDeArbol {
    public function dibujar(int $x, int $y): void;
}

// 2. Flyweight Concreto (guarda el estado intrínseco)
class ArbolFlyweight implements TipoDeArbol {
    // Estado intrínseco: compartido e inmutable
    private string $nombre;
    private string $textura; // Simula un archivo pesado (20MB)

    public function __construct(string $nombre, string $textura) {
        $this->nombre = $nombre;
        $this->textura = $textura;
        echo "   [FLYWEIGHT] Creado: $this->nombre (Textura: $this->textura)\n";
    }

    // Recibe el estado extrínseco (posición) como parámetro
    public function dibujar(int $x, int $y): void {
        echo "   [DIBUJO] $this->nombre en ($x, $y) con textura $this->textura\n";
    }
}

// 3. Factoría del Flyweight (caché de objetos)
class FactoriaArbol {
    /** @var TipoDeArbol[] */
    private array $flyweights = [];

    public function getFlyweight(string $nombre, string $textura): TipoDeArbol {
        if (!isset($this->flyweights[$nombre])) {
            // No existe, lo creamos
            $this->flyweights[$nombre] = new ArbolFlyweight($nombre, $textura);
        }
        // Ya existe, lo devolvemos (¡ahorro!)
        return $this->flyweights[$nombre];
    }

    public function getNumFlyweights(): int {
        return count($this->flyweights);
    }
}

// 4. Uso del patrón (El Motor del Juego)
$factoria = new FactoriaArbol();

echo "--- 3 Robles (mismo tipo, distinta posición) ---\n";
$factoria->getFlyweight('Roble', 'roble_hd.jpg')->dibujar(10, 50);
$factoria->getFlyweight('Roble', 'roble_hd.jpg')->dibujar(20, 60);
$factoria->getFlyweight('Roble', 'roble_hd.jpg')->dibujar(30, 70);

echo "\n--- 2 Abetos (nuevo tipo) ---\n";
$factoria->getFlyweight('Abeto', 'abeto_hd.png')->dibujar(5, 10);
$factoria->getFlyweight('Abeto', 'abeto_hd.png')->dibujar(8, 12);

echo "\n--- Resumen ---\n";
echo "Árboles solicitados: 5\n";
echo "Flyweights en memoria: " . $factoria->getNumFlyweights() . "\n";
// Solo 2 objetos en memoria, no 5. ¡Ahorro del 60%!

// 🖥️ Salida:
// --- 3 Robles (mismo tipo, distinta posición) ---
//    [FLYWEIGHT] Creado: Roble (Textura: roble_hd.jpg)
//    [DIBUJO] Roble en (10, 50) con textura roble_hd.jpg
//    [DIBUJO] Roble en (20, 60) con textura roble_hd.jpg
//    [DIBUJO] Roble en (30, 70) con textura roble_hd.jpg
//
// --- 2 Abetos (nuevo tipo) ---
//    [FLYWEIGHT] Creado: Abeto (Textura: abeto_hd.png)
//    [DIBUJO] Abeto en (5, 10) con textura abeto_hd.png
//    [DIBUJO] Abeto en (8, 12) con textura abeto_hd.png
//
// --- Resumen ---
// Árboles solicitados: 5
// Flyweights en memoria: 2

[FLYWEIGHT] Creado” solo sale dos veces: una para Roble y otra para Abeto. Las demás llamadas reutilizan el que ya existe. Si tuvieras 10.000 robles, seguirías teniendo un solo Flyweight en memoria. Eso es lo bonito del asunto.

¿Por qué es un error guardar la posición (X, Y) dentro del Flyweight?


🌍 5. Ejemplo real en PHP: Caché de iconos

Los jueguecitos están bien para explicar, pero en backend PHP esto también se usa. Imagina un sistema que genera PDFs con iconos:

class IconoFlyweight {
    private string $nombre;
    private string $svgData; // El SVG puede pesar lo suyo

    public function __construct(string $nombre, string $svgData) {
        $this->nombre = $nombre;
        $this->svgData = $svgData;
    }

    public function renderizar(int $tamaño, string $color): string {
        // Estado intrínseco: svgData (compartido)
        // Estado extrínseco: tamaño y color (único por uso)
        return "<svg width='$tamaño' fill='$color'>{$this->svgData}</svg>";
    }
}

class FactoriaIconos {
    private array $iconos = [];

    public function getIcono(string $nombre): IconoFlyweight {
        if (!isset($this->iconos[$nombre])) {
            // Simula cargar el SVG del disco
            $svg = file_get_contents("/icons/$nombre.svg");
            $this->iconos[$nombre] = new IconoFlyweight($nombre, $svg);
        }
        return $this->iconos[$nombre];
    }
}

// Uso: un PDF con 500 líneas, cada una con el icono "check"
$factoria = new FactoriaIconos();

foreach ($lineasDelPdf as $linea) {
    $icono = $factoria->getIcono('check'); // Siempre el mismo objeto
    echo $icono->renderizar(16, $linea['color']); // Distinto color
}
// El SVG se cargó del disco UNA vez, no 500

Otro caso muy típico son los sistemas que renderizan fuentes tipográficas. Cada carácter tiene un glyph (la forma visual del carácter), que es el estado intrínseco. La posición, tamaño y color son el estado extrínseco. Un documento con 100.000 caracteres no necesita 100.000 glyphs en memoria, solo los del alfabeto (~52). Lo demás son posiciones.

¿En qué caso real de PHP backend podrías aplicar Flyweight?


📊 6. Números reales: ¿Cuánto ahorra?

Para que veas que no exagero, aquí tienes los números del ejemplo de los árboles:

EscenarioSin FlyweightCon FlyweightAhorro
100 árboles (2 tipos)100 × 20MB = 2.000 MB2 × 20MB + 100 × 24B = ~40 MB98%
10.000 árboles (5 tipos)10.000 × 20MB = 200 GB5 × 20MB + 10.000 × 24B = ~100 MB99.95%
1.000.000 caracteres (52 glyphs)1M × 50KB = 50 GB52 × 50KB + 1M × 8B = ~10 MB99.98%

Cuantos más objetos tengas y más pese el estado intrínseco, más bestial es el ahorro. Pero ojo que si tus objetos pesan poco y son pocos, Flyweight es matar moscas a cañonazos.


🔄 7. Comparativa con otros patrones

PatrónPropósitoDiferencia clave
FlyweightCompartir estado entre muchos objetos para ahorrar memoriaMúltiples objetos comparten una instancia inmutable
SingletonGarantizar una sola instancia globalSingleton = una instancia global; Flyweight = varias instancias compartidas (una por tipo)
PrototypeClonar objetos existentesPrototype clona (más objetos); Flyweight comparte (menos objetos)
ProxyControlar el acceso a un objetoProxy controla acceso; Flyweight controla memoria
Object PoolReutilizar objetos costosos de crearPool reutiliza objetos mutables; Flyweight comparte objetos inmutables

La confusión que más veo es Flyweight vs Singleton. La diferencia es que un Singleton es siempre una sola instancia global. Flyweight puede tener varias (una por tipo: un Flyweight para “Roble”, otro para “Abeto”, etc.). La Factory decide cuál te toca.

¿Cuál es la diferencia principal entre Flyweight y Singleton?


⚖️ 8. Relación con SOLID

SRP (Single Responsibility)

Cada pieza a lo suyo: el Flyweight guarda el estado compartido, la Factoría gestiona instancias y el Cliente guarda el estado único. Nadie se mete en el trabajo del otro.

OCP (Open/Closed)

¿Quieres un tipo nuevo de Flyweight (ej. "Palmera")? Lo creas y la Factoría lo gestiona solita. Sin tocar nada de lo que ya tienes.

DIP (Dependency Inversion)

El cliente depende de la interfaz TipoDeArbol, no de ArbolFlyweight directamente. Puedes cambiar la implementación y no se rompe nada.

LSP (Liskov Substitution)

Cualquier implementación de TipoDeArbol encaja donde se espera la interfaz. El Flyweight concreto se puede sustituir sin dramas.


✅ 9. Ventajas y desventajas

Ventajas
  • Ahorro brutal de memoria: Miles de objetos, una sola instancia compartida del estado pesado.
  • Rendimiento: Menos objetos = menos trabajo para el recolector de basura, menos inicialización.
  • Transparente: El cliente ni se entera de que está compartiendo objetos. Para él es como si fueran únicos.
  • Escala de miedo: Funciona igual de bien con 100 que con 1.000.000 de objetos.
Desventajas
  • Complejidad añadida: Separar estado intrínseco y extrínseco no es lo más natural del mundo.
  • Incomodidad: El cliente tiene que andar pasando el estado extrínseco en cada llamada.
  • Inmutabilidad obligatoria: El Flyweight no puede tener setters ni cambiar por dentro. Si lo haces, la lías.
  • Overkill si hay pocos objetos: La Factoría y toda la infraestructura no compensan si solo tienes 10 cositas.

⚠️ 10. Errores comunes

1. Colar estado extrínseco dentro del Flyweight

Si el Flyweight guarda datos que son diferentes para cada uso, ya no se puede compartir y te cargas el patrón entero:

// ❌ MAL: la posición es única, no pinta nada en el Flyweight
class ArbolFlyweight {
    private string $textura;
    private int $x; // ← Esto es estado extrínseco
    private int $y; // ← Esto también

    public function dibujar(): void {
        // Usa $this->x y $this->y directamente
    }
}

// ✅ BIEN: la posición se pasa como parámetro
class ArbolFlyweight {
    private string $textura; // Solo estado intrínseco

    public function dibujar(int $x, int $y): void {
        // Recibe x,y como parámetros (estado extrínseco)
    }
}

2. Saltarse la Factoría

Si el cliente crea Flyweights a pelo con new, no hay compartición ninguna. Como si no existiera el patrón:

// ❌ MAL: crea objetos nuevos cada vez
$roble1 = new ArbolFlyweight('Roble', 'roble.jpg');
$roble2 = new ArbolFlyweight('Roble', 'roble.jpg'); // ¡Duplicado!

// ✅ BIEN: siempre a través de la Factoría
$roble1 = $factoria->getFlyweight('Roble', 'roble.jpg');
$roble2 = $factoria->getFlyweight('Roble', 'roble.jpg'); // Misma instancia

3. Flyweight con setters (mutable)

Si dejas que alguien cambie el estado interno del Flyweight, se lía parda. Recuerda que ese objeto lo comparten TODOS:

// ❌ MAL: el Flyweight tiene setters
class ArbolFlyweight {
    public function setTextura(string $textura): void {
        $this->textura = $textura; // ¡Cambia para TODOS los que lo comparten!
    }
}

// Si cambias la textura del "Roble", TODOS los robles del juego cambian.
// Probablemente no es lo que quieres, nene.

El Flyweight tiene que ser inmutable. Si necesitas cambiar algo, crea un nuevo tipo.

¿Qué pasa si el Flyweight permite modificar su estado interno (tiene setters)?

4. Usarlo cuando no hace falta

Si solo tienes 20 objetos ligeros, montar la Factory y separar el estado es trabajar de más para nada. Flyweight es un patrón de optimización, no lo metas por si acaso:

// ❌ Sobreingeniería: 5 botones con un icono de 2KB
$factoria->getFlyweight('boton-guardar', 'save.svg');

// ✅ Para 5 botones, crea los objetos normales y déjate de historias
$boton = new Boton('Guardar', 'save.svg');

🤔 11. ¿Cuándo usarlo y cuándo no?

Úsalo cuando...
  • Tienes miles o millones de objetos que se parecen mucho entre sí
  • Los objetos tienen datos pesados que se repiten (texturas, fuentes, configuraciones)
  • Puedes separar claramente el estado intrínseco del extrínseco
  • El estado intrínseco es inmutable (no cambia una vez creado)
  • Has medido que la memoria es un problema real, no una suposición
Evítalo cuando...
  • Tienes pocos objetos (la movida no compensa)
  • Los objetos son todos diferentes (no hay nada que compartir)
  • El estado compartido es mutable (necesita cambiar según la instancia)
  • La memoria no es un problema real — no optimices lo que no has medido

Si has perfilado tu aplicación y ves que cientos de objetos comparten los mismos datos pesados, tira de Flyweight. Si no has medido que la memoria es un problema, probablemente no lo necesitas. No seas ése que optimiza prematuramente.

¿Cuándo NO deberías usar Flyweight?


💡 12. Conclusión

El Flyweight es un patrón de optimización pura y dura. No te cambia la arquitectura ni te añade funcionalidad nueva. Lo que hace es que tu aplicación funcione donde antes se quedaba tiesa por falta de memoria.

La idea consiste en guardar solamente una vez lo que se repite. Lo que varía, pásalo como parámetro. Es lo que hacen los motores de videojuegos con las texturas, los procesadores de texto con las fuentes y los navegadores con los estilos CSS.

No es de los patrones que usas todos los días. Pero el día que lo necesitas, te marca la diferencia entre una aplicación que tira y una que se cae a la puta.

EA, ¡saluditos y nos vemos en los bares! 🍻