O padrão Flyweight é um padrão de projeto estrutural focado na eficiência de memória. Seu objetivo principal é reduzir o consumo de recursos ao lidar com um grande volume de objetos similares, compartilhando o máximo de dados possível entre eles em vez de manter todos os dados em cada objeto individualmente.
Conceitos Fundamentias: Estado Intrínseco e Extrínseco
A implementação eficaz do Flyweight baseia-se na distinção entre dois tipos de dados:
- Estado Intrínseco: Informações constantes que podem ser compartilhadas entre vários objetos. Este estado é armazenado dentro do objeto Flyweight.
- Estado Extrínseco: Informações que variam dependendo do contexto e não podem ser compartilahdas. Este estado deve ser passado ao objeto via parâmetros quando necessário.
Componentes do Padrão
- Flyweight Abstrato: Define a interface através da qual os flyweights podem receber e atuar sobre o estado extrínseco.
- Flyweight Concreto: Implementa a interface e armazena o estado intrínseco.
- Fábrica Flyweight (Factory): Gerencia o pool de objetos. Quando um cliente solicita um objeto, a fábrica verifica se ele já existe; se sim, retorna a instância existente; caso contrário, cria uma nova.
Exemplo Prático: Sistema de Renderização de Peças
Imagine um jogo estilo Tetris. Criar milhares de instâncias de cada bloco pode sobrecarregar a memória. Usaremos o Flyweight para gerenciar as formas dos blocos como estados intrínsecos.
// Flyweight Abstrato
public abstract class FormaBase {
public abstract String getTipo();
public void renderizar(String cor) {
System.out.println("Bloco: " + getTipo() + " | Cor: " + cor);
}
}
// Flyweights Concretos
public class BlocoT extends FormaBase {
@Override
public String getTipo() {
return "Formato T";
}
}
public class BlocoZ extends FormaBase {
@Override
public String getTipo() {
return "Formato Z";
}
}
// Fábrica Flyweight com Singleton
public class GerenciadorDeBlocos {
private final Map<String, FormaBase> blocos = new HashMap<>();
private GerenciadorDeBlocos() {}
private static class Holder {
private static final GerenciadorDeBlocos INSTANCIA = new GerenciadorDeBlocos();
}
public static GerenciadorDeBlocos getInstancia() {
return Holder.INSTANCIA;
}
public FormaBase buscarBloco(String chave) {
if (!blocos.containsKey(chave)) {
switch (chave) {
case "T": blocos.put("T", new BlocoT()); break;
case "Z": blocos.put("Z", new BlocoZ()); break;
}
}
return blocos.get(chave);
}
}
Vantagens e Desvantagens
Vantagens
- Redução significativa da pegada de memória em aplicações com muitos objetos semelhantes.
- Melhoria na performance global do sistema ao evitar alocações excessivas.
Desvantagens
- Aumenta a complexidade do código ao separar os estados.
- Pode haver uma pequena perda de tempo de processamento ao calcular ou passar o estado extrínseco.
Análise de Uso no JDK: A Classe Integer
O Java utiliza o Flyweight internamente na classe Integer através do método valueOf(). Existe um cache para números pequenos, o que evita a criação de novos objetos desnecessariamente.
public class TesteCache {
public static void main(String[] args) {
Integer a = 100;
Integer b = 100;
// true: Ambos apontam para a mesma instância no cache
System.out.println("a == b? " + (a == b));
Integer x = 200;
Integer y = 200;
// false: Fora do intervalo padrão de cache (-128 a 127)
System.out.println("x == y? " + (x == y));
}
}
Ao analisar o código fonte da classe Integer, observamos a implementação do IntegerCache:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
Este mecanismo garante que valores frequentemente utilizados sejam reutilizados, economizando memória e tempo de execução, o que é a essência do padrão Flyweight.