Pular para o conteúdo principal

Flyweight

Descrição​

O Flyweight reduz o consumo de memória quando uma aplicação precisa manter um número muito grande de objetos parecidos. A ideia é compartilhar, entre muitos contextos, as partes do estado que se repetem, em vez de duplicá-las em cada instância.

Para isso, o estado do objeto é dividido em duas partes: o estado intrínseco, imutável e independente do contexto, que fica armazenado no flyweight e pode ser compartilhado; e o estado extrínseco, que varia conforme o uso e é mantido pelo cliente (ou por um objeto de contexto) e passado como parâmetro às operações do flyweight.

Uma FlyweightFactory controla a criação e o reaproveitamento das instâncias: ao receber uma chave, devolve o flyweight já existente no cache ou cria um novo. Assim, milhares de objetos lógicos podem ser representados por poucas instâncias físicas.

Problemas que resolve​

  • Consumo excessivo de memória causado por milhares ou milhões de objetos que carregam dados idênticos (fontes, texturas, ícones, metadados)
  • Custo de criação repetida de objetos pesados cujo conteúdo é sempre o mesmo
  • Dificuldade de escalar estruturas de granularidade fina (caracteres de um editor, partículas, árvores de um mapa, células de planilha)

Quando usar​

  • A aplicação cria uma quantidade muito grande de objetos semelhantes e a memória é um gargalo real
  • Grande parte do estado dos objetos pode ser extraída e tornada extrínseca
  • Muitos grupos de objetos podem ser substituídos por poucos objetos compartilhados depois que o estado extrínseco é removido
  • A aplicação não depende da identidade dos objetos (comparações por referência não são relevantes)

Quando evitar​

  • O número de objetos é pequeno ou a memória não é uma restrição; o ganho não compensa a complexidade
  • O estado dos objetos é majoritariamente único, sobrando pouco a compartilhar
  • O estado intrínseco precisa ser mutável, o que tornaria o compartilhamento perigoso
  • Calcular ou transportar o estado extrínseco custa mais (CPU, legibilidade) do que a memória economizada

Trade-offs​

GanhoCusto / Risco
Redução drástica do uso de memóriaCódigo mais complexo, com estado dividido entre flyweight e contexto
Menos alocações e menor pressão sobre o garbage collectorCusto de CPU para buscar no cache e recalcular/passar o estado extrínseco
Ponto central de criação e reaproveitamento via factoryA factory e seu cache precisam ser thread-safe em ambientes concorrentes
Objetos compartilhados imutáveis são seguros entre threadsPerda de identidade: o mesmo objeto representa vários elementos lógicos
  • Acoplamento: clientes passam a depender da factory e precisam conhecer o estado extrínseco.
  • Testabilidade: flyweights imutáveis são fáceis de testar; o cache compartilhado pode exigir isolamento entre testes.
  • Desempenho: é uma otimização; meça antes de aplicar e cuide do crescimento ilimitado do cache.

Diagrama​

O Client obtém flyweights pela FlyweightFactory, que mantém um cache de ConcreteFlyweight compartilhados (com estado intrínseco). O cliente guarda o estado extrínseco e o repassa em cada chamada a operation; UnsharedConcreteFlyweight representa implementações que não precisam ser compartilhadas.

Exemplo​

Um jogo renderiza um milhão de árvores em um mapa. Nome, cor e textura (pesados) são compartilhados por tipo de árvore; apenas as coordenadas ficam em cada árvore.

record TreeType(String name, String color, byte[] texture) { // flyweight (intrínseco)
void draw(Canvas canvas, int x, int y) {
canvas.paint(texture, color, x, y);
}
}

class TreeTypeFactory {
private static final Map<String, TreeType> cache = new ConcurrentHashMap<>();

static TreeType get(String name, String color, byte[] texture) {
return cache.computeIfAbsent(name + ":" + color,
k -> new TreeType(name, color, texture));
}
}

record Tree(int x, int y, TreeType type) { // contexto (extrínseco)
void draw(Canvas canvas) { type.draw(canvas, x, y); }
}

class Forest {
private final List<Tree> trees = new ArrayList<>();

void plant(int x, int y, String name, String color, byte[] texture) {
trees.add(new Tree(x, y, TreeTypeFactory.get(name, color, texture)));
}

void draw(Canvas canvas) { trees.forEach(t -> t.draw(canvas)); }
}

Padrões relacionados​

  • Composite — folhas de uma árvore Composite costumam ser implementadas como flyweights compartilhados
  • Factory Method — a FlyweightFactory encapsula a criação e o reaproveitamento das instâncias
  • Singleton — a factory de flyweights frequentemente é única; ambos controlam o número de instâncias
  • Object Pool — também reaproveita objetos, mas por empréstimo exclusivo, enquanto o Flyweight compartilha instâncias imutáveis simultaneamente
  • State e Strategy — objetos de estado ou estratégia sem dados próprios podem ser compartilhados como flyweights
  • Proxy — ambos interpõem um objeto para controlar recursos; o Proxy controla o acesso, o Flyweight controla a quantidade de instâncias

Referência​