Neste artigo, analisamos como padrões de projeto clássicos são implementados de forma prática no Java Development Kit (JDK), demonstrando como a flexibilidade na aplicação desses padrões resolve problemas reais no desenvolvimento de software.
Padrão Fábrica na Classe Calendar
Embora muitas classes de fábrica sejam nomeadas com o sufixo "Factory", no JDK a abordagem é mais integrada. A classe java.util.Calendar exemplifica isso ao combinar funcionalidades de negócios com um método fábrica estático. O método getInstance() atua como fábrica, criando instâncias de subclasses específicas com base em parâmetros como fuso horário e localidade, sem expor os detalhes da criação ao cliente.
public abstract class Calendario implements Serializable, Cloneable, Comparable<Calendario> {
// ... código omitido para brevidade ...
public static Calendario obterInstancia(FusoHorario zona, Localidade local) {
return fabricarCalendario(zona, local);
}
private static Calendario fabricarCalendario(FusoHorario zona, Localidade local) {
// Lógica de criação baseada em locale e fuso horário
if (local.obterTipoUnicode("ca") != null) {
String tipo = local.obterTipoUnicode("ca");
if ("budista".equals(tipo)) {
return new CalendarioBudista(zona, local);
} else if ("japones".equals(tipo)) {
return new CalendarioImperialJapones(zona, local);
}
}
// Fallback para calendário gregoriano
return new CalendarioGregoriano(zona, local);
}
}
Padrão Construtor na Classe Calendar
A mesma classe Calendar também emprega o padrão Construtor através de uma classe interna estática Builder. Enquanto o método fábrica cria objetos diferentes, o Construtor permite a construção passo a passo de um único tipo complexo com múltiplas opções configuráveis. O processo de construção no método build() combina inicialmente lógica similar à fábrica para selecionar a subclasse, seguida pela configuração detalhada dos campos.
public class Calendario {
// ... outras partes da classe ...
public static class Construtor {
private long instante;
private int[] campos;
private String tipo;
private FusoHorario zona;
private boolean flexivel = true;
private Localidade local;
public Construtor definirInstante(long instante) {
this.instante = instante;
return this;
}
// Outros métodos de definição omitidos
public Calendario construir() {
if (local == null) local = Localidade.padrao();
if (zona == null) zona = FusoHorario.padrao();
Calendario cal;
if (tipo == null) tipo = local.obterTipoUnicode("ca");
// Seleção da subclasse baseada no tipo
switch (tipo) {
case "gregoriano":
cal = new CalendarioGregoriano(zona, local, true);
break;
case "budista":
cal = new CalendarioBudista(zona, local);
break;
default:
cal = new CalendarioGregoriano(zona, local, true);
}
// Configuração adicional do objeto
cal.definirFlexivel(flexivel);
// ... lógica de configuração de campos ...
return cal;
}
}
}
A coexistência de fábrica e construtor na mesma classe ilustra que os padrões não são regras rígidas. A escolha depende das necessidades específicas: criar objetos de tipos variados versus construir um objeto complexo com configurações detalhadas.
Padrão Decorador na Classe Collections
A classe utilitária Collections aplica o padrão Decorador para adicionar funcionalidades a coleções exsitentes. Por exemplo, o método estático colecaoNaoModificavel() retorna uma instância decorada que restringe operações de modificação. A classe decoradora encapsula uma coleção subjacente, delegando a maioria das operações, mas lançando exceções em métodos como adicionar().
public class Colecoes {
public static <E> Colecao<E> colecaoNaoModificavel(Colecao<? extends E> colecaoOriginal) {
return new ColecaoDecorada<>(colecaoOriginal);
}
static class ColecaoDecorada<E> implements Colecao<E>, Serializable {
private final Colecao<? extends E> colecaoBase;
ColecaoDecorada(Colecao<? extends E> base) {
if (base == null) throw new NullPointerException();
this.colecaoBase = base;
}
public int tamanho() { return colecaoBase.tamanho(); }
public boolean contem(Object obj) { return colecaoBase.contem(obj); }
// Métodos de leitura delegados
public boolean adicionar(E elemento) {
throw new UnsupportedOperationException();
}
// Métodos de modificação bloqueados
}
}
Esta implementação não é uma simples herança, mas um envolvimento que altera o comportamento, caracterizando um decorador.
Padrão Adaptador na Classe Collections
Para manter compatibilidade com versões antigas, o JDK usa o padrão Adaptador. A interface antiga Enumeracao é adaptada para funcionar com o novo iterador Iterador. O método estático enumeracao() na classe Collections retorna uma classe anônima que implementa Enumeracao delegando as chamadas para um Iterador.
public interface Iterador<E> {
boolean temProximo();
E proximo();
}
public class Colecoes {
public static <E> Enumeracao<E> enumeracao(final Colecao<E> colecao) {
return new Enumeracao<E>() {
private final Iterador<E> iterador = colecao.iterador();
public boolean temMaisElementos() {
return iterador.temProximo();
}
public E proximoElemento() {
return iterador.proximo();
}
};
}
}
Este adaptador permite que código legado continue operando sem modificações, enquanto o JDK evolui internamente.
A análise do JDK revela que a aplicação de padrões de projeto frequentemente envolve adaptações criativas. Em vez de seguir implementações canônicas, os desenvolvedores combinam e modificam padrões para atender a requisitos específicos, como desempenho, compatibilidade ou clareza de código. Essa flexibilidade é essencial para soluções de software eficazes no mundo real.