EstruturalAvançado6 min de leitura9 seções1994, Marco de referência: a ideia é mais velha que a data

Flyweight

Reduz o consumo de memória compartilhando entre muitos objetos a parte do estado que é igual em todos, mantendo fora apenas o que varia de instância para instância.

Em 10 segundos

Cem mil árvores não precisam de cem mil texturas: elas precisam de duas, e de cem mil pares de coordenadas. O padrão separa o que se repete (compartilhado e imutável) do que varia (passado por parâmetro) e troca memória por uma indireção.

  • #memoria
  • #compartilhamento
  • #cache
  • #gof
Analogia

Os tipos móveis da gráfica

Uma tipografia não funde um bloco de metal novo para cada letra impressa no livro. Ela tem um tipo para o 'a' e o reutiliza milhares de vezes; o que muda a cada uso é a posição na página, não a letra. O molde é caro e único; a posição é barata e infinita.

01Conceito

O problema

Quando o número de objetos vai à casa das centenas de milhares, o que era desprezível vira decisivo. Alguns quilobytes duplicados por objeto se tornam gigabytes.

O detalhe importante é onde está o desperdício: quase nunca no que diferencia os objetos, quase sempre no que eles têm em comum.

A divisão entre intrínseco e extrínseco é a decisão de projeto do padrão, e não é óbvia. Intrínseco é o que pode ser compartilhado sem que ninguém perceba: precisa ser imutável e independente do contexto. Extrínseco é tudo o que dá identidade à instância — posição, estado, dono.

Como o objeto compartilhado não pode guardar o extrínseco, ele deixa de ser autossuficiente: as operações passam a receber por parâmetro o que antes era campo. Isso muda a assinatura dos métodos e é o principal impacto do padrão na legibilidade.

Vale distinguir de Singleton: ali existe uma instância única por decisão de projeto, para controlar acesso. Aqui existem várias instâncias compartilhadas por valor, para economizar memória — a fábrica devolve uma por espécie, não uma no total. E de cache: o objetivo do cache é evitar recomputar; o do flyweight é evitar duplicar.

02Conceito

Meça antes, sempre

Este é, entre os padrões do GoF, o mais claramente motivado por desempenho — e por isso o mais sujeito a ser aplicado sem necessidade.

Linguagens modernas já compartilham mais do que se imagina. Strings literais são internadas, valores pequenos são reaproveitados, e coletores de lixo compactam objetos. Boa parte do ganho que o padrão prometia em 1994 hoje vem de graça.

Antes de adotar, o número que importa é quanto da memória está em estado duplicado. Se cem mil objetos ocupam 800 MB e o intrínseco compartilhável representa 90% disso, o padrão é a resposta. Se representa 5%, ele só vai adicionar indireção e complicar a leitura.

Vale considerar alternativas antes: estruturas orientadas a dados (arrays paralelos em vez de objetos), tipos de valor, __slots__ em Python, ou simplesmente não materializar todos os objetos ao mesmo tempo. Às vezes a resposta certa não é compartilhar, é não criar.

Sem o padrão × com o padrão

Com Flyweight: o comum é compartilhado

  • duas texturas para 100.000 árvores
  • cada árvore guarda só x, y e uma referência
  • memória proporcional à variedade, não à quantidade
  • métodos recebem o estado extrínseco por parâmetro

O custo é legibilidade e disciplina: o flyweight precisa ser imutável, e o objeto deixa de ser autoexplicativo porque parte do seu estado vive fora dele.

A troca é memória por indireção e clareza. Só compensa depois de medir — e a medida que importa é quanto do consumo está em estado que se repete.
Como se organiza
  • FabricaTiposuma instância por espécie, no máximo

    • TipoArvore 'pinheiro'intrínseco — textura pesada, imutável

    • TipoArvore 'carvalho'intrínseco — compartilhado por 50 mil árvores

  • Arvore ×100.000extrínseco — só x, y e a referência ao tipo

Cem mil objetos leves apontando para dois pesados. A fábrica é o que garante que o segundo pedido de 'pinheiro' devolva o mesmo objeto do primeiro.
Passo a passoO fluxo, passo a passo
  1. 1

    Separar o estado

    Intrínseco (igual e imutável) fica no flyweight; extrínseco (posição, dono) fica fora.

  2. 2

    Criar pela fábrica

    A fábrica devolve a mesma instância para a mesma chave — sem duplicar o pesado.

  3. 3

    Contexto leve

    Cada instância guarda só o extrínseco + a referência ao flyweight.

  4. 4

    Passar o extrínseco

    Nas operações, o contexto envia x/y (etc.) por parâmetro ao flyweight.

03Anatomia

Navegue pelas camadas

Camada 1/3Flyweight

Camada 1

Flyweight

o estado que se repete

Guarda apenas o intrínseco e precisa ser imutável — ele é compartilhado por milhares de contextos, e uma alteração afetaria todos de uma vez. Suas operações recebem o extrínseco por parâmetro.

No código

class TipoArvore { constructor(readonly especie: string, readonly textura: string) {} }

Se violar: flyweight com campo mutável faz uma árvore alterar a aparência de todas as outras da mesma espécie.

04Estrutura

Estrutura

Diagrama de classes
05Implementação

Código

// Flyweight: so o estado INTRINSECO (igual para milhares de arvores)
class TipoArvore {
  constructor(
    readonly especie: string,
    readonly textura: string, // imagina alguns MB
  ) {}

  // o estado EXTRINSECO chega por parametro
  desenhar(x: number, y: number): void {
    console.log(`${this.especie} em (${x},${y})`);
  }
}

// Fabrica: garante uma instancia por especie
class FabricaTipos {
  private cache = new Map<string, TipoArvore>();

  obter(especie: string, textura: string): TipoArvore {
    let t = this.cache.get(especie);
    if (!t) {
      t = new TipoArvore(especie, textura);
      this.cache.set(especie, t);
    }
    return t;
  }

  get tamanho(): number {
    return this.cache.size;
  }
}

// Contexto: leve, so guarda o que e proprio + referencia ao flyweight
class Arvore {
  constructor(
    private x: number,
    private y: number,
    private tipo: TipoArvore,
  ) {}
  desenhar(): void {
    this.tipo.desenhar(this.x, this.y);
  }
}

const fabrica = new FabricaTipos();
const floresta: Arvore[] = [];
for (let i = 0; i < 100_000; i++) {
  const especie = i % 2 ? "pinheiro" : "carvalho";
  floresta.push(new Arvore(i, i, fabrica.obter(especie, "textura...")));
}

console.log(floresta.length, "árvores usando", fabrica.tamanho, "texturas"); // 100000 / 2
06No mundo real

Casos de uso

Vegetação em cenário de jogo

Cenário

Um mapa aberto tem centenas de milhares de árvores, arbustos e pedras, com poucas dezenas de modelos e texturas distintos entre eles.

Como o padrão entra

Modelo e textura viram flyweights compartilhados; cada instância guarda apenas posição, rotação e escala. É a base do rendering por instanciação nas placas de vídeo.

Trade-off: Ganha-se memória e perde-se a possibilidade de variar detalhes por instância sem quebrar o compartilhamento — dar uma cor levemente diferente a uma árvore específica exige tirá-la do grupo ou mover a cor para o extrínseco.

Glifos em editor de texto

Cenário

Um documento com um milhão de caracteres não pode ter um objeto completo por caractere, com fonte, tamanho e métricas replicados.

Como o padrão entra

Cada glifo de uma fonte é um flyweight com sua forma e métricas; o documento guarda a sequência de códigos e as posições. É o exemplo original do GoF, e continua válido.

Trade-off: Formatação por trecho (negrito numa palavra) precisa ser representada fora dos glifos, normalmente como intervalos — o que torna operações de edição no meio do texto bem mais complicadas do que seriam com objetos completos.

Metadados de eventos em telemetria

Cenário

Um coletor processa milhões de eventos por minuto, e cada um carrega nome, tipo, unidade e conjunto de rótulos que se repetem muito.

Como o padrão entra

Os descritores de métrica são internados numa tabela; cada evento guarda a referência e apenas valor e timestamp. A economia é grande porque a variedade de descritores é baixa.

Trade-off: A tabela de internamento cresce com a cardinalidade e vira vazamento se as chaves forem geradas dinamicamente (um rótulo com id de usuário, por exemplo) — o cache passa a guardar tudo para sempre.

O flyweight mutável

Compila, roda, passa no code review. E quebra depois.

O compartilhamento está lá — e o estado intrínseco ainda aceita escrita. Uma alteração num contexto pinta todos os outros da mesma chave.

class TipoArvore {
  constructor(
    public especie: string,
    public textura: string,
    public corTintura = "natural", // <- mutavel e compartilhado
  ) {}

  pintar(cor: string) { this.corTintura = cor; }
  desenhar(x: number, y: number) {
    console.log(this.especie, this.corTintura, x, y);
  }
}

const pinheiro = new TipoArvore("pinheiro", "tex...");
// milhares de arvores apontam para o MESMO pinheiro
pinheiro.pintar("vermelho");
// todas as arvores da especie mudaram de cor — sem aviso.

O que quebra

  • Na telaÁrvores 'mudam de cor sozinhas' — o sintoma clássico de estado compartilhado mutável.
  • No testeUm caso pinta o tipo e o próximo herda a cor sem resetar a fábrica.
  • Ao escalarQuanto mais compartilhamento, mais larga a blast radius de cada mutação.

A saída: Estado intrínseco imutável por construção (readonly, congelar, sem setters). Variação por instância (cor, escala) vai para o extrínseco — no contexto, não no flyweight.

08Cuidado

Armadilhas

  1. Flyweight mutável

    É o erro fatal do padrão. Como o objeto é compartilhado por milhares de contextos, qualquer alteração se propaga para todos eles — e o sintoma aparece como 'algumas árvores mudaram de cor sozinhas'. O estado intrínseco precisa ser imutável por construção, não por convenção.

  2. Cache que nunca esvazia

    A fábrica guarda flyweights indefinidamente. Se a chave tiver cardinalidade alta ou vier de entrada do usuário, o cache cresce sem limite e vira vazamento de memória — justamente no padrão adotado para economizar memória. Chaves precisam ter domínio conhecido e limitado, ou o cache precisa de descarte.

  3. Adotar sem medir

    O padrão piora a legibilidade: métodos ganham parâmetros, objetos deixam de ser autossuficientes e surge uma fábrica obrigatória. Pagar esse preço sem um número que justifique é otimização prematura no sentido mais literal. Meça o consumo, identifique quanto está duplicado e só então decida.

09Decisão

Quando usar × quando evitar

Use quando

3
  • Há muitos objetos semelhantes e a memória é o gargalo medido.
  • Boa parte do estado se repete e pode ser imutável.
  • A identidade individual dos objetos não importa.

Evite quando

3
  • A quantidade de objetos é modesta.
  • O estado é quase todo único por instância.
  • Você ainda não mediu onde a memória está indo.

Testar o que ficou

3 armadilhas de Flyweight viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • Interning de stringRuntimes reaproveitam a mesma string literal em memória em vez de guardar mil cópias iguais.
  • Symbol.forO registro global devolve o mesmo símbolo para a mesma chave, em qualquer parte do programa.
  • React.memoEvita recriar a saída quando as props não mudaram — o estado extrínseco decide, o intrínseco se reaproveita.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Estado intrínseco compartilhado; extrínseco por instância.
const glyph = GlyphFactory.obter("A", fonte);

O que ele cobra

1 salto a mais entre a chamada e o efeito.

  • Separar estado intrínseco de extrínseco complica o modelo e é fácil de errar
  • O pool de objetos compartilhados vira estado global, com todos os cuidados que isso exige

Não vale se: não há muitos objetos quase iguais consumindo memória — sem esse volume, a economia não paga a complexidade.

Onde isto aparece

Continue por aqui