GoF, p. 195 também chamado de Cache
Peso-Mosca
Flyweight
Compartilhe a parte imutável entre milhões de objetos e economize memória.
Usar compartilhamento para suportar eficientemente grandes quantidades de objetos de granularidade fina.
A floresta do videogame
Um jogo desenha 500 000 árvores. Cada árvore tem posição própria — mas a textura, a malha 3D e a cor são idênticas para todas as árvores da mesma espécie. Guardar a textura 500 000 vezes estoura a memória. Guarda-se uma vez por espécie, e cada árvore no mapa carrega só x, y e um ponteiro para a espécie.
Você precisa de um número enorme de objetos semelhantes e a memória não aguenta. A maior parte do que cada objeto guarda é idêntica entre eles.
Separe o estado em duas partes: o **intrínseco** (imutável, compartilhável — a textura) e o **extrínseco** (varia por instância — a posição). Guarde só o intrínseco no flyweight, compartilhado via uma fábrica com cache; passe o extrínseco como parâmetro dos métodos.
- Milhares de objetos idênticos campo a campo no heap dump.
- Strings ou buffers duplicados aos milhares.
Estrutura
- Flyweight
- Guarda o estado intrínseco compartilhado; deve ser imutável.
- FlyweightFactory
- Mantém o pool e devolve o flyweight existente, criando só se não houver.
- Context
- Guarda o estado extrínseco e uma referência ao flyweight.
- Client
- Calcula/armazena o estado extrínseco.
Implementação
// ── Flyweight: SÓ o estado intrínseco, imutável ───────
class TipoDeArvore {
constructor(nome, cor, textura) {
this.nome = nome; this.cor = cor; this.textura = textura; // ~2 KB cada
Object.freeze(this); // imutável: pode ser compartilhado sem medo
}
// O estado EXTRÍNSECO chega por parâmetro — nunca é armazenado aqui.
desenhar(canvas, x, y) { /* canvas.draw(this.textura, x, y) */ }
}
// ── Fábrica: garante o compartilhamento ───────────────
class FabricaDeTipos {
static #pool = new Map();
static obter(nome, cor, textura) {
const chave = `${nome}|${cor}`;
if (!this.#pool.has(chave)) this.#pool.set(chave, new TipoDeArvore(nome, cor, textura));
return this.#pool.get(chave); // ← mesma instância para todos
}
static get total() { return this.#pool.size; }
}
// ── Context: leve, um por árvore no mapa ──────────────
class Arvore {
constructor(x, y, tipo) { this.x = x; this.y = y; this.tipo = tipo; } // ~24 bytes
desenhar(canvas) { this.tipo.desenhar(canvas, this.x, this.y); }
}
class Floresta {
arvores = [];
plantar(x, y, nome, cor, textura) {
this.arvores.push(new Arvore(x, y, FabricaDeTipos.obter(nome, cor, textura)));
}
}
const floresta = new Floresta();
const especies = [['Carvalho', '#2d5016'], ['Pinheiro', '#1a3d0c'], ['Ipê', '#f2c94c']];
for (let i = 0; i < 1_000_000; i++) {
const [nome, cor] = especies[i % 3];
floresta.plantar(i % 1920, (i * 7) % 1080, nome, cor, '<blob de 2KB>');
}
console.log(floresta.arvores.length); // 1 000 000 contextos leves
console.log(FabricaDeTipos.total); // 3 flyweights — ~2 GB viraram ~6 KB - java.lang.Integer.valueOf() — cache de -128 a 127
- String interning na JVM e em outras runtimes
- Glyphs de fonte em editores de texto; tiles em jogos 2D
Consequências
- Redução drástica de memória quando há muitos objetos semelhantes.
- Menos pressão no coletor de lixo.
- Troca memória por CPU: o estado extrínseco precisa ser recalculado ou repassado a cada chamada.
- O código fica bem mais difícil de ler — a entidade “árvore” virou duas classes.
- Só compensa em escala; é uma otimização, não um padrão de design de domínio.
- Você tem MUITOS objetos, a memória é o gargalo real, e você já mediu isso.
- Boa parte do estado dos objetos é duplicada e pode ser extraída como imutável.
- A quantidade de objetos é modesta — é otimização prematura pura.
- O estado dos objetos é majoritariamente único.
Relações com outros padrões
- Composite
- Folhas compartilhadas de uma árvore Composite são flyweights naturais.
- Abstract Factory
- A fábrica de flyweights é uma fábrica com cache.
Verificação
O que é o estado EXTRÍNSECO de um flyweight?
- O estado que varia por instância e é passado por parâmetro, nunca armazenado no flyweight
- O estado compartilhado e imutável guardado dentro do flyweight
- O cache mantido pela fábrica
- O estado persistido em banco de dados
Intrínseco = compartilhado e imutável, mora no flyweight (a textura). Extrínseco = único por uso, mora no contexto ou vem como argumento (a posição x, y).