Flyweight Pattern para Comparticion Eficiente de Objetos a Gran Escala
Usa el Flyweight pattern para minimizar uso de memoria compartiendo la mayor cantidad de datos posible entre objetos similares, esencial para renderizar datasets grandes
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Flyweight Pattern para Comparticion Eficiente de Objetos a Gran Escala
El Flyweight pattern minimiza el uso de memoria compartiendo la mayor cantidad de datos posible entre objetos similares. Cuando una aplicacion necesita crear miles de objetos que comparten la mayor parte de su estado, Flyweight extrae el estado compartido (intrinseco) en un objeto compartido separado, dejando solo el estado unico (extrinseco) en cada instancia.
Cuando Usar Esto
- Una aplicacion usa una gran cantidad de objetos con estado compartido
- El costo de memoria es alto por la cantidad de objetos
- La mayor parte del estado del objeto puede hacerse extrinseco y computarse on the fly
Problema
Un editor de documentos con 100,000 caracteres crea 100,000 objetos Character. Cada uno almacena fuente, tamano, color y datos de glifo — incluso cuando solo existen 200 estilos de caracter unicos en el documento.
Solucion
// flyweight/CharacterStyle.ts
interface CharacterStyle {
font: string;
size: number;
color: string;
bold: boolean;
}
class StyleFactory {
private styles = new Map<string, CharacterStyle>();
getStyle(font: string, size: number, color: string, bold: boolean): CharacterStyle {
const key = `${font}-${size}-${color}-${bold}`;
if (!this.styles.has(key)) {
this.styles.set(key, { font, size, color, bold });
}
return this.styles.get(key)!;
}
getStyleCount(): number {
return this.styles.size;
}
}
// Flyweight character con posicion extrinseca
class Character {
constructor(
private char: string,
private style: CharacterStyle // Shared intrinsic state
) {}
render(position: number): string {
// Estado extrinseco: posicion pasada en tiempo de renderizado
return `<span style="font: ${this.style.size}px ${this.style.font}; color: ${this.style.color}; ${this.style.bold ? 'font-weight: bold;' : ''}" data-position="${position}">${this.char}</span>`;
}
}
// Documento usa flyweights
class Document {
private characters: { char: Character; position: number }[] = [];
private styleFactory = new StyleFactory();
insert(char: string, position: number, font: string, size: number, color: string, bold: boolean): void {
const style = this.styleFactory.getStyle(font, size, color, bold);
const character = new Character(char, style);
this.characters.push({ char: character, position });
}
render(): string {
return this.characters
.map(c => c.char.render(c.position))
.join('');
}
getMemoryStats(): { characters: number; uniqueStyles: number } {
return {
characters: this.characters.length,
uniqueStyles: this.styleFactory.getStyleCount(),
};
}
}
// Uso
const doc = new Document();
// Insertar 10,000 caracteres usando solo 3 estilos unicos
doc.insert('H', 0, 'Arial', 12, '#000', true);
doc.insert('e', 1, 'Arial', 12, '#000', true);
for (let i = 2; i < 10000; i++) {
doc.insert('x', i, 'Arial', 12, '#000', false);
}
console.log(doc.getMemoryStats());
// { characters: 10000, uniqueStyles: 2 }
Variacion: Pool de Objetos de Juego
// flyweight/Tree.ts
interface TreeType {
mesh: string;
barkTexture: string;
leafTexture: string;
}
class TreeTypeFactory {
private types = new Map<string, TreeType>();
getTreeType(mesh: string, bark: string, leaf: string): TreeType {
const key = `${mesh}-${bark}-${leaf}`;
if (!this.types.has(key)) {
this.types.set(key, { mesh, barkTexture: bark, leafTexture: leaf });
}
return this.types.get(key)!;
}
}
// Instancia de Tree solo almacena posicion y referencia de tipo
class Tree {
constructor(
private x: number,
private y: number,
private type: TreeType // Shared flyweight
) {}
render(): void {
console.log(`Render ${this.type.mesh} at (${this.x}, ${this.y})`);
}
}
// Bosque con miles de arboles usando pocos tipos
class Forest {
private trees: Tree[] = [];
private typeFactory = new TreeTypeFactory();
plantTree(x: number, y: number, mesh: string, bark: string, leaf: string): void {
const type = this.typeFactory.getTreeType(mesh, bark, leaf);
this.trees.push(new Tree(x, y, type));
}
}
Como Funciona
- Flyweight almacena el estado intrinseco (compartido) que pertenece a muchos objetos
- Context almacena el estado extrinseco (unico) y referencia un Flyweight
- Flyweight Factory crea y maneja instancias de flyweight compartidas
- Client computa estado extrinseco y lo pasa a los metodos del flyweight
Consideraciones de Produccion
- Los flyweights deben ser inmutables; nunca modifiques estado compartido despues de la creacion
- La seguridad de threads es requerida cuando la factory se accede concurrentemente
- Considera usar WeakMap para garbage collection automatico de flyweights no usados
Errores Comunes
- Poner estado extrinseco dentro de la clase Flyweight, derrotando el proposito
- No usar una factory, permitiendo instancias duplicadas de flyweight
- Modificar estado de flyweight compartido, corrompiendo todos los contexts que lo usan
FAQ
P: En que se diferencia de un cache? R: Flyweight es una decision a nivel de diseno sobre estructura de objetos. Un cache es una optimizacion para datos arbitrarios. Los flyweights son parte del modelo de dominio.
P: Cuando NO deberia usar Flyweight? R: Cuando el numero de estados compartidos se aproxima al numero de instancias, o cuando computar estado extrinseco es mas costoso que almacenarlo directamente.