💾 El Patrón Flyweight: El gran ahorrador de memoria
El Patrón Flyweight (peso ligero) pertenece a la familia de los patrones estructurales y resuelve un problema muy común: ¿Qué haces cuando necesitas muchísimos objetos (cientos!, miles! millones!) que son muy parecidos y tu aplicación comienza a consumir muchísima memoria?
El objetivo principal de Flyweight es minimizar el uso de memoria o los costes de cpu compartiendo tanta información como sea posible entre varios objetos.
La respuesta es no crear copias de las partes idénticas. En su lugar, creamos una única instancia compartida (el Flyweight) y hacemos que todos los objetos que la necesiten, la referencien. Es decir, compartimos la memoria.
💾 1. Un ejemplo sencillo: Las matrículas de los coches
Imagina una fábrica que produce miles de coches:
- Sin Flyweight (desperdicio): Cada coche es un objeto único. Si cada coche tiene una misma imagen del logo de la marca (un archivo de 5MB) incrustada en su objeto, la memoria se dispara. ¡1000 coches * 5MB = 5000 MB (5GB)! 😵💫
- Con Flyweight (ahorro): Distinguimos entre dos tipos de datos:
- Estado Intrínseco (compartible): La información que es idéntica para todos, como el archivo de la imagen del logo de la marca, el modelo del motor, o el color estándar. Esta información se guarda en una sola instancia de Flyweight.
- Estado Extrínseco (único): La información que es única para cada coche, como su matrícula y la posición GPS actual. Esta información se pasa al objeto Flyweight cuando se le pide.
Concepto (analogía) | Rol en Flyweight | Tarea principal |
|---|---|---|
La imagen del logo de la marca | Estado intrínseco (Flyweight) | La información compartida e inmutable que ahorra memoria. |
La matrícula y el GPS | Estado extrínseco | La información única que el cliente pasa al Flyweight. |
La fábrica de Flyweights | Factoría del Flyweight | Asegura que solo se cree una instancia de cada Flyweight. |
El secreto está en que el Flyweight solo guarda lo que es común. La información única se la tiene que dar el Cliente cuando llama a sus métodos.
🛠️ 2. Los tres pilares del patrón Flyweight
El Flyweight necesita tres roles principales para funcionar correctamente:
1. El Flyweight (El objeto compartido)
- Define la interfaz para los objetos que pueden recibir un estado extrínseco.
- Guarda el estado intrínseco (lo que se comparte y es constante).
2. La factoría del Flyweight (el catálogo)
- Se encarga de crear y, lo más importante, gestionar las instancias de Flyweight.
- Si un Flyweight ya existe (lo comprueba con una clave), lo devuelve. Si no existe, lo crea y lo guarda para futuras peticiones. Esto garantiza la unicidad.
3. El cliente
- Guarda el estado extrínseco (la información única de cada objeto).
- Llama al método del Flyweight y le pasa este estado único como argumento.
🤔 1. ¿Cuál es el principal objetivo del Patrón Flyweight?
🎮 3. El ejemplo clásico: Un juego con muchos árboles
Imagina un juego de rol (RPG) donde hay miles de árboles. Todos los árboles “Roble” tienen la misma textura de tronco (una imagen de 20MB) y el mismo modelo 3D.
| Clase/Rol | Función (Ej. dibujar) |
|---|---|
| Árbol Roble Flyweight | Guarda la textura (20MB) y el modelo 3D (Estado Intrínseco). |
| Cliente (El Motor del Juego) | Guarda la posición (X, Y, Z) de cada árbol (Estado Extrínseco). |
Clase/Rol | Función (Ej. |
|---|---|
Flyweight (objeto compartido) | Contiene la lógica de |
Factoría de Flyweight | Asegura que si ya existe un Flyweight “Roble”, lo devuelve sin crear la instancia de 20MB de nuevo. |
Si hay 1000 Robles, en lugar de 1000 copias de la textura (20.000 MB), solo hay una (20 MB), y 1000 objetos pequeños que guardan la posición y referencian a esa única textura. ¡Ahorro brutal!
🤔 2. ¿Qué tipo de información debe contener el objeto Flyweight (estado intrínseco)?
✅ 4. ¿Por qué usarlo?
El Flyweight es un patrón de optimización de rendimiento y memoria:
- Ahorro de memoria: Es el beneficio clave. Si tienes millones de objetos, este patrón puede hacer que tu aplicación funcione donde antes colapsaba.
- Optimización: Al tener menos instancias de objetos, el tiempo de inicialización de la aplicación y la gestión de la memoria por el sistema (recolección de basura) se reducen drásticamente.
- Transparencia: El cliente solo se preocupa por qué Flyweight quiere (por ejemplo, “un Roble”), no de cómo se crea o si ya existe. La Factoría se encarga de todo.
❌ 5. Desventaja a considerar
- Añade complejidad: Tienes que separar el estado de los objetos en dos partes (intrínseco y extrínseco), lo cual no es natural. Además, necesitas una Factoría que gestione todo.
- Gestión del estado extrínseco: El cliente ahora es responsable de pasar la información única (el estado extrínseco) en cada llamada. Esto puede ser incómodo y propenso a errores si se olvida.
🤔 3. ¿Cuál es el rol de la Factoría del Flyweight?
💡 6. Conclusión
El Patrón Flyweight es una herramienta esencial cuando la escasez de memoria o el alto número de objetos son un problema. Es un patrón de alto nivel que no se usa tan a menudo como el Proxy o el Decorator, pero cuando realmente se necesita es cuando se nota su valor.
Si tienes una colección enorme de objetos con muchas propiedades que se repiten, no lo dudes e implementa el Flyweight para “volar ligero” y ganar rendimiento.
🧠 7. Ejemplo Práctico en PHP
Vamos a implementar el patrón Flyweight con el ejemplo de los árboles en un juego. La información del tipo de árbol (modelo, textura) será el estado intrínseco (Flyweight) y la posición será el estado extrínseco (gestionado por el cliente).
<?php
// 🔹 1. El Flyweight (El Objeto Compartido)
interface TipoDeArbol {
public function dibujar(int $x, int $y): void;
}
// 🔹 2. El Flyweight Concreto (Estado Intrínseco - la parte compartida)
class ArbolFlyweight implements TipoDeArbol {
private string $nombre;
private string $textura; // Simula un objeto pesado, por ejemplo, un archivo de 20MB
public function __construct(string $nombre, string $textura) {
$this->nombre = $nombre;
$this->textura = $textura;
echo " [FLYWEIGHT] Creando nuevo objeto: Tipo $this->nombre (Textura: $this->textura). Ahorrando memoria en futuras instancias." . PHP_EOL;
}
/**
* El método del Flyweight que recibe el estado Extrínseco (posición)
*/
public function dibujar(int $x, int $y): void {
// Usa el estado intrínseco (textura) y el estado extrínseco (posición)
echo " [DIBUJO] Dibujando Arbol $this->nombre (Textura: $this->textura) en Posición ($x, $y)." . PHP_EOL;
}
}
// 🔹 3. La Factoría del Flyweight (La que asegura la unicidad)
class FactoriaArbol {
// Almacena las instancias Flyweight existentes, indexadas por el tipo de árbol (la clave).
private array $flyweights = [];
/**
* Devuelve un Flyweight existente o crea uno nuevo si no existe.
*/
public function getFlyweight(string $nombre, string $textura): TipoDeArbol {
if (!isset($this->flyweights[$nombre])) {
// El Flyweight no existe, lo creamos e indexamos.
$this->flyweights[$nombre] = new ArbolFlyweight($nombre, $textura);
}
// El Flyweight ya existe, simplemente lo devolvemos para compartirlo.
return $this->flyweights[$nombre];
}
public function getNumFlyweights(): int {
return count($this->flyweights);
}
}
// 🔹 4. Uso del patrón (El Cliente - El Motor del Juego)
$factoria = new FactoriaArbol();
// Creamos 3 Robles. Solo se crea 1 objeto Flyweight.
echo "--- Creando 3 Robles (Mismo Tipo, Diferente Posición) ---" . PHP_EOL;
// La Factoría crea el primer Flyweight 'Roble'
$roble1 = $factoria->getFlyweight('Roble', 'textura_roble.jpg');
$roble1->dibujar(10, 50);
// La Factoría DEVUELVE el mismo Flyweight 'Roble' (¡Ahorro!)
$roble2 = $factoria->getFlyweight('Roble', 'textura_roble.jpg');
$roble2->dibujar(20, 60);
// La Factoría DEVUELVE el mismo Flyweight 'Roble' (¡Ahorro!)
$roble3 = $factoria->getFlyweight('Roble', 'textura_roble.jpg');
$roble3->dibujar(30, 70);
echo PHP_EOL . "--- Creando 2 Abetos (Nuevo Tipo de Arbol) ---" . PHP_EOL;
// La Factoría crea el primer Flyweight 'Abeto'
$abeto1 = $factoria->getFlyweight('Abeto', 'textura_abeto.png');
$abeto1->dibujar(5, 10);
// La Factoría DEVUELVE el mismo Flyweight 'Abeto' (¡Ahorro!)
$abeto2 = $factoria->getFlyweight('Abeto', 'textura_abeto.png');
$abeto2->dibujar(8, 12);
echo PHP_EOL . "--- Resumen de Memoria ---" . PHP_EOL;
echo "Se han solicitado 5 árboles en total (3 Robles, 2 Abetos)." . PHP_EOL;
echo "Número REAL de objetos Flyweight creados y en memoria: " . $factoria->getNumFlyweights() . PHP_EOL;
// 🖥️ Salida del programa:
// --- Creando 3 Robles (Mismo Tipo, Diferente Posición) ---
// [FLYWEIGHT] Creando nuevo objeto: Tipo Roble (Textura: textura_roble.jpg). Ahorrando memoria en futuras instancias.
// [DIBUJO] Dibujando Arbol Roble (Textura: textura_roble.jpg) en Posición (10, 50).
// [DIBUJO] Dibujando Arbol Roble (Textura: textura_roble.jpg) en Posición (20, 60).
// [DIBUJO] Dibujando Arbol Roble (Textura: textura_roble.jpg) en Posición (30, 70).
// --- Creando 2 Abetos (Nuevo Tipo de Arbol) ---
// [FLYWEIGHT] Creando nuevo objeto: Tipo Abeto (Textura: textura_abeto.png). Ahorrando memoria en futuras instancias.
// [DIBUJO] Dibujando Arbol Abeto (Textura: textura_abeto.png) en Posición (5, 10).
// [DIBUJO] Dibujando Arbol Abeto (Textura: textura_abeto.png) en Posición (8, 12).
// --- Resumen de Memoria ---
// Se han solicitado 5 árboles en total (3 Robles, 2 Abetos).
// Número REAL de objetos Flyweight creados y en memoria: 2
EA, ¡saluditos y nos vemos en los bares! 🍻