Pular para o conteúdo

O Flyweight é um padrão de projeto estrutural voltado para economia de memória. Ele faz isso ao compartilhar a parte comum de vários objetos semelhantes, em vez de repetir os mesmos dados em cada instância.

Esse padrão fica especialmente relevante quando o sistema precisa manter muitos objetos pequenos ao mesmo tempo, mas boa parte do estado deles se repete. Em vez de cada objeto carregar tudo, o Flyweight separa o que pode ser compartilhado do que depende do contexto de uso.

Imagine um sistema acadêmico com um mapa interativo do campus. Esse mapa exibe milhares de elementos na tela:

  • salas de aula
  • laboratórios
  • bibliotecas
  • setores administrativos
  • pontos de apoio ao estudante

Cada marcador do mapa possui informações que se repetem bastante:

  • ícone visual do tipo de local
  • cor associada à categoria
  • imagem ou sprite do marcador
  • configuração visual padrão

Ao mesmo tempo, cada marcador também possui dados únicos:

  • coordenada x
  • coordenada y
  • nome do local
  • bloco ou andar
  • status atual de disponibilidade

Sem um desenho adequado, a aplicação pode criar milhares de objetos completos, cada um carregando tanto os dados únicos quanto os dados repetidos. Isso gera problemas práticos:

  • maior consumo de memória
  • duplicação desnecessária de dados idênticos
  • pior escalabilidade em telas com muitos elementos
  • desperdício ao carregar o mesmo recurso visual várias vezes
  • dificuldade para otimizar renderização

O problema central não é apenas ter muitos objetos. O problema é repetir dentro de cada objeto um estado que poderia ser compartilhado entre instâncias semelhantes.

O Flyweight resolve isso separando o estado do objeto em duas partes:

  1. estado intrínseco: parte comum, estável e compartilhável
  2. estado extrínseco: parte variável, contextual e específica de cada uso

No exemplo do mapa do campus:

  • tipo do marcador, cor e ícone podem virar estado intrínseco
  • posição, nome do local e disponibilidade ficam como estado extrínseco

Em vez de criar um objeto completo para cada marcador, o sistema passa a:

  1. criar poucos objetos compartilhados com o estado intrínseco
  2. reutilizar esses objetos em vários contextos
  3. manter o estado extrínseco fora do flyweight
  4. passar o contexto necessário no momento da operação

Com isso, milhares de marcadores podem compartilhar poucos objetos pesados. O ponto central do padrão é este: compartilhe o que se repete e mantenha fora do flyweight aquilo que muda em cada instância.

Pense em cadeiras de uma sala de aula virtual exibida em um simulador. O modelo visual da cadeira, sua cor e seu formato podem ser exatamente iguais para centenas de posições. O que muda é onde a cadeira está no mapa e se ela está ocupada ou livre.

No Flyweight, o desenho da cadeira pode ser compartilhado. A posição e o estado de ocupação são informados no contexto em que ela aparece.

Use Flyweight quando:

  • a aplicação cria muitos objetos semelhantes
  • boa parte do estado desses objetos é repetida
  • o consumo de memória virou um problema real
  • faz sentido separar estado compartilhado de estado contextual
  • os objetos compartilhados podem ser tratados como imutáveis

Exemplos reais em software:

  • mapa com milhares de marcadores visuais
  • editor com caracteres que compartilham fonte e estilo
  • jogo com milhares de partículas ou árvores
  • sistema gráfico com ícones repetidos em dashboards
  • simulação com muitos objetos do mesmo tipo

Flyweight costuma ser mal utilizado quando a equipe tenta aplicá-lo cedo demais, sem evidências de que existe um problema real de memória. Alguns erros frequentes são:

  • usar Flyweight com poucos objetos, sem ganho prático
  • separar estado de forma artificial só para seguir o catálogo de padrões
  • deixar o estado compartilhado mutável
  • complicar demais o modelo ao mover contexto para muitos lugares
  • ignorar o custo extra de coordenar estado intrínseco e extrínseco

As consequências incluem código mais difícil de entender, maior risco de bugs e otimização prematura. Flyweight funciona melhor como otimização consciente, não como padrão introdutório para qualquer estrutura repetida.

Uma forma incremental de implementar Flyweight é:

  1. identifique uma classe que exista em grande quantidade na memória
  2. separe o que é repetido do que muda por instância
  3. mova o estado compartilhável para uma classe flyweight
  4. torne o flyweight imutável
  5. crie uma fábrica para reutilizar flyweights existentes
  6. mantenha o estado extrínseco no cliente ou em um objeto de contexto
  7. passe o contexto necessário ao chamar os métodos do flyweight

Antes de escrever o código, vale responder: há muitos objetos quase iguais, e o custo de repeti-los na memória realmente incomoda? Se a resposta for sim, Flyweight pode ser uma boa escolha.

No exemplo abaixo, vários locais do mapa compartilham o mesmo estilo visual por tipo de ambiente.

import java.util.HashMap;
import java.util.Map;
class TipoLocalFlyweight {
private final String categoria;
private final String cor;
private final String icone;
public TipoLocalFlyweight(String categoria, String cor, String icone) {
this.categoria = categoria;
this.cor = cor;
this.icone = icone;
}
public void desenhar(int x, int y, String nomeLocal) {
System.out.println(
"Desenhando " + categoria + " '" + nomeLocal +
"' em (" + x + ", " + y + ") com cor " + cor +
" e icone " + icone
);
}
}
class TipoLocalFactory {
private static final Map<String, TipoLocalFlyweight> tipos = new HashMap<>();
public static TipoLocalFlyweight obter(String categoria, String cor, String icone) {
String chave = categoria + ":" + cor + ":" + icone;
if (!tipos.containsKey(chave)) {
tipos.put(chave, new TipoLocalFlyweight(categoria, cor, icone));
}
return tipos.get(chave);
}
}
class LocalMapa {
private final int x;
private final int y;
private final String nome;
private final TipoLocalFlyweight tipo;
public LocalMapa(int x, int y, String nome, TipoLocalFlyweight tipo) {
this.x = x;
this.y = y;
this.nome = nome;
this.tipo = tipo;
}
public void renderizar() {
tipo.desenhar(x, y, nome);
}
}

Nesse código, TipoLocalFlyweight guarda apenas o estado compartilhado. Já LocalMapa guarda o contexto de cada instância, como posição e nome. Assim, vários locais podem reutilizar o mesmo objeto de tipo visual.

  • reduz consumo de memória em coleções grandes
  • evita duplicação de dados repetidos
  • melhora reaproveitamento de objetos compartilhados
  • deixa explícita a diferença entre estado comum e contextual
  • funciona bem com fábricas de cache e reutilização
  • aumenta a complexidade do modelo
  • exige separação cuidadosa entre estados
  • pode trocar memória por mais indireção e coordenação
  • dificulta manutenção se usado sem necessidade real
  • flyweights mutáveis podem causar bugs graves de compartilhamento

O Flyweight se relaciona com outros padrões importantes:

  • Composite: folhas de uma árvore Composite podem ser compartilhadas como flyweights quando muitos elementos são semelhantes
  • Facade: Facade simplifica acesso a um subsistema; Flyweight otimiza representação de muitos objetos parecidos
  • Singleton: Singleton possui uma única instância global; Flyweight normalmente possui várias instâncias compartilhadas por chave
  • Factory Method / Abstract Factory: fábricas podem ajudar a centralizar criação e reutilização de flyweights
  • Imutabilidade: o estado intrínseco costuma precisar ser imutável para ser compartilhado com segurança

Essas comparações ajudam a perceber que Flyweight não existe para simplificar interface nem para garantir instância única, mas para reduzir redundância de memória ao compartilhar estado comum.

As formas mais comuns de aplicar Flyweight são:

  • uso de uma fábrica explícita com cache por chave
  • compartilhamento de estilos, fontes ou ícones em interfaces gráficas
  • representação de tipos de partícula, tile ou terreno em simulações
  • objetos de contexto pequenos apontando para um flyweight maior

Em frameworks gráficos, motores de jogo e sistemas de renderização, Flyweight aparece com frequência quando muitos elementos usam os mesmos recursos visuais ou metadados.

Agora veja um caso mais completo em um sistema universitário. O mapa do campus precisa renderizar vários locais, mas deve reutilizar o estilo visual dos tipos repetidos.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class TipoMarcadorFlyweight {
private final String categoria;
private final String cor;
private final String icone;
public TipoMarcadorFlyweight(String categoria, String cor, String icone) {
this.categoria = categoria;
this.cor = cor;
this.icone = icone;
}
public void desenhar(String nome, int x, int y, boolean disponivel) {
System.out.println(
categoria + " - " + nome +
" em (" + x + ", " + y + ")" +
" | cor=" + cor +
" | icone=" + icone +
" | disponivel=" + disponivel
);
}
}
class TipoMarcadorFactory {
private final Map<String, TipoMarcadorFlyweight> cache = new HashMap<>();
public TipoMarcadorFlyweight obter(String categoria, String cor, String icone) {
String chave = categoria + ":" + cor + ":" + icone;
if (!cache.containsKey(chave)) {
cache.put(chave, new TipoMarcadorFlyweight(categoria, cor, icone));
}
return cache.get(chave);
}
public int totalCompartilhado() {
return cache.size();
}
}
class MarcadorCampus {
private final String nome;
private final int x;
private final int y;
private final boolean disponivel;
private final TipoMarcadorFlyweight tipo;
public MarcadorCampus(String nome, int x, int y, boolean disponivel, TipoMarcadorFlyweight tipo) {
this.nome = nome;
this.x = x;
this.y = y;
this.disponivel = disponivel;
this.tipo = tipo;
}
public void renderizar() {
tipo.desenhar(nome, x, y, disponivel);
}
}
public class MapaCampus {
public static void main(String[] args) {
TipoMarcadorFactory factory = new TipoMarcadorFactory();
List<MarcadorCampus> marcadores = new ArrayList<>();
TipoMarcadorFlyweight sala = factory.obter("Sala", "Azul", "sala.png");
TipoMarcadorFlyweight laboratorio = factory.obter("Laboratorio", "Verde", "lab.png");
TipoMarcadorFlyweight biblioteca = factory.obter("Biblioteca", "Laranja", "biblioteca.png");
marcadores.add(new MarcadorCampus("Sala A101", 10, 20, true, sala));
marcadores.add(new MarcadorCampus("Sala A102", 14, 20, false, sala));
marcadores.add(new MarcadorCampus("Lab Redes", 30, 18, true, laboratorio));
marcadores.add(new MarcadorCampus("Lab Software", 32, 18, false, laboratorio));
marcadores.add(new MarcadorCampus("Biblioteca Central", 50, 12, true, biblioteca));
for (MarcadorCampus marcador : marcadores) {
marcador.renderizar();
}
System.out.println("Tipos compartilhados criados: " + factory.totalCompartilhado());
}
}

Nesse exemplo, o sistema cria vários MarcadorCampus, mas compartilha poucos objetos TipoMarcadorFlyweight. O estado visual repetido fica centralizado e imutável, enquanto o contexto de cada local permanece no objeto que representa aquela ocorrência no mapa.

Esse tipo de modelagem funciona bem quando a aplicação precisa lidar com grandes quantidades de objetos parecidos, especialmente em cenários visuais, simulações e interfaces com muitos elementos repetidos.