Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Patrones de Diseño Estructurales

Flyweight

Compartir datos entre múltiples objetos

Universidad Nacional de Rio Negro - Sede Andina

Definición

El patrón Flyweight usa compartición para soportar grandes cantidades de objetos granulares eficientemente, separando estado intrínseco (compartido) del extrínseco (particular).

Origen e Historia

Gang of Four 1994. Surge de optimización de memoria en juegos/gráficos. Inspirado en el concepto de “compartir” datos comunes. Popularizado en motores de videojuegos y editores de texto donde miles/millones de objetos similares requieren memoria mínima.

Motivación

Necesario cuando:

Contexto

Patrón: Estado intrínseco (compartido) vs. extrínseco (particular)

Anatomía:

Ejemplo: 1M árboles = 1 flyweight de “Pino” + 1M referencias

Cuando aplica

Ideal para:

Cuando no aplica

Evitá Flyweight cuando:

Consecuencias de su uso

Positivas

Negativas

Alternativas

PatrónPropósitoDiferencia
CompositeComponer en árbolEstructura jerárquica
ProxyControlar accesoUno-a-uno
Object PoolReutilizar objetosPatrón concurrencia

Estructura

Problema

// ❌ Millones de árboles: cada uno copia su forma y textura
class Árbol {
    private String especie;        // "Pino", "Roble" - repetido!
    private String textura;        // Datos de imagen - repetido!
    private double x, y, z;        // Posición única
    private double altura;         // Altura única
}

// 1 millón de árboles × 2MB cada uno = 2GB de memoria!
List<Árbol> bosque = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
    bosque.add(new Árbol("Pino", "textura.png", x, y, z, altura));
}

Solución

/**
 * Datos compartidos (Flyweight): estado intrínseco.
 */
public class TipoÁrbol {
    private final String especie;
    private final byte[] texturaComprimida;
    
    public TipoÁrbol(String especie, byte[] textura) {
        this.especie = especie;
        this.texturaComprimida = textura;
    }
    
    public String getEspecie() { return especie; }
    public byte[] getTextura() { return texturaComprimida; }
}

/**
 * Factory para compartir Flyweights.
 */
public class FábricaTipoÁrbol {
    private static final Map<String, TipoÁrbol> tipos = new HashMap<>();
    
    public static TipoÁrbol obtenerTipo(String especie, byte[] textura) {
        if (!tipos.containsKey(especie)) {
            tipos.put(especie, new TipoÁrbol(especie, textura));
        }
        return tipos.get(especie);
    }
}

/**
 * Árbol con estado extrínseco (posición, altura).
 */
public class Árbol {
    private TipoÁrbol tipo;     // Compartido (Flyweight)
    private double x, y, z;     // Extrínseco
    private double altura;      // Extrínseco
    
    public Árbol(TipoÁrbol tipo, double x, double y, double z, double altura) {
        this.tipo = tipo;
        this.x = x;
        this.y = y;
        this.z = z;
        this.altura = altura;
    }
    
    public void dibujar() {
        System.out.println("Dibujando " + tipo.getEspecie() + 
                         " en (" + x + "," + y + "," + z + "), altura: " + altura);
    }
}

/**
 * Bosque que reutiliza Flyweights.
 */
public class Bosque {
    private List<Árbol> árboles = new ArrayList<>();
    
    public void plantarÁrbol(String especie, double x, double y, double z, double altura) {
        TipoÁrbol tipo = FábricaTipoÁrbol.obtenerTipo(especie, obtenerTextura(especie));
        Árbol árbol = new Árbol(tipo, x, y, z, altura);
        árboles.add(árbol);
    }
    
    public void dibujarBosque() {
        for (Árbol árbol : árboles) {
            árbol.dibujar();
        }
    }
    
    private byte[] obtenerTextura(String especie) {
        return new byte[1024 * 10]; // 10 KB por tipo (compartido)
    }
}

// ✅ Uso eficiente
Bosque bosque = new Bosque();
for (int i = 0; i < 1_000_000; i++) {
    bosque.plantarÁrbol("Pino", Math.random() * 1000, Math.random() * 1000, 0, 20);
}
bosque.dibujarBosque();

Diagramas

Diagrama de Clases

Diagrama de Secuencia

Ejemplos

Ejemplo 1: Editor de Texto (Caracteres)

public class Carácter {
    private final char valor;
    private final String fuente;
    private final int tamaño;
    
    public Carácter(char valor, String fuente, int tamaño) {
        this.valor = valor;
        this.fuente = fuente;
        this.tamaño = tamaño;
    }
    
    public void renderizar(int x, int y) {
        System.out.println("Renderizando '" + valor + "' en (" + x + "," + y + 
                         ") con " + fuente);
    }
}

public class FábricaCarácter {
    private static final Map<Character, Carácter> caracteres = new HashMap<>();
    
    public static Carácter obtener(char valor) {
        if (!caracteres.containsKey(valor)) {
            caracteres.put(valor, new Carácter(valor, "Arial", 12));
        }
        return caracteres.get(valor);
    }
}

public class Documento {
    private List<Carácter> contenido = new ArrayList<>();
    
    public void agregarCarácter(char c) {
        contenido.add(FábricaCarácter.obtener(c));
    }
    
    public void mostrar() {
        int x = 0;
        for (Carácter c : contenido) {
            c.renderizar(x++, 0);
        }
    }
}

// Uso: 1M caracteres pero solo 256 Flyweights máximo
Documento doc = new Documento();
doc.agregarCarácter('H');
doc.agregarCarácter('o');
doc.agregarCarácter('l');
doc.agregarCarácter('a');
doc.mostrar();

Ejercicios

Resumen

El patrón Flyweight es crítico para aplicaciones que manejan cantidades masivas de objetos similares. Al separar estado compartible del estado particular, logra reducir dramáticamente el consumo de memoria. Su uso requiere cuidadosa identificación de estado intrínseco vs. extrínseco, pero el ahorro resultante es significativo en sistemas a gran escala.

Próximo paso

Para seguir, conviene pasar a el material siguiente, donde el recorrido continúa sobre esta base.