Você está na página 1de 11

Mundo OO coluna

Padrões de Projeto com Generics


Aprenda como tirar vantagem de Generics na implementação de
Padrões de Projeto em suas aplicações Java

Padrões de Projeto documentam soluções comuns para problemas recorrentes em


Engenharia de Software. Muitos dos padrões de projeto catalogados acabam sendo,
de uma forma ou de outra, implementados por frameworks, e conhecê-los é de grande
valia para qualquer desenvolvedor. Neste artigo, mostraremos como alguns padrões de
projeto podem tirar vantagem dos tipos genéricos oferecidos pela linguagem Java. Os
tipos genéricos, ou Generics, permitem a criação de código genérico quanto aos tipos
de dados manipulados em programas Java. Como veremos, a combinação de padrões
de projeto com Generics pode levar nossas aplicações a um nível maior de flexibilidade
e facilidade de manutenção, sem abrir mão da segurança de tipos.

O objetivo deste artigo é mostrar como alguns padrões de projeto podem ser implemen-
tados utilizando os recursos oferecidos por Generics. Apresentaremos as vantagens e
desvantagens advindas dessa abordagem para cada padrão considerado.

O uso de Generics em Java torna possível implementarmos nossas interfaces, classes e métodos
de forma genérica quanto aos tipos de dados manipulados, deixando para o compilador a tarefa
Alexandre Gazola de fazer a associação adequada de tipos. Com Generics, podemos obter um elevado grau de reu-
(alexandregazola@gmail.com): tilização e flexibilidade em nossas aplicações, sem abrir mão da segurança advinda da tipagem
é bacharel em Ciência da Computação pela
estática. Seu uso foi incorporado a diversas classes das APIs padrão da linguagem Java, além de ser
Universidade Federal de Viçosa (UFV) e mestre em
Informática pela PUC-Rio. Trabalha como analista empregado extensamente em vários frameworks.
de Sistemas no BNDES, e possui as certificações
SCJP, SCWCD e CSM. Padrões de Projeto apresentam a essência de uma solução de projeto para problemas recorrentes
em Engenharia de Software. Como definido na literatura, um padrão nomeia, abstrai e identifica
aspectos-chave de uma estrutura de projeto comum, definindo participantes, papéis, colaborações
e responsabilidades entre as entidades que compõem o projeto. Padrões nos ajudam a construir
sistemas de software seguindo bons princípios de OO, como alta coesão e baixo acoplamento,
resultando em sistemas mais flexíveis e de mais fácil manutenção. A principal referência quando
se fala em padrões de projeto é a obra da gangue dos quatro (GoF, gang of four): “Design Patterns:
Elements of Reusable Object-Oriented Software”, o qual apresenta um catálogo com 23 padrões
de projeto para problemas corriqueiros no projeto de sistemas de software orientados a objetos.
Outra obra clássica na área de padrões de software é a série “Pattern-Oriented Software Archi-
tecture” (POSA), que já está em seu quinto volume. Com o passar do tempo, a comunidade de
software tem catalogado diversos outros tipos de padrões, tais como padrões de arquitetura de
aplicações corporativas, padrões de integração, padrões para testes etc. As referências ao final do
artigo trazem algumas das principais referências em padrões para projeto de software. O artigo
Alex Marques Campos “Padrões de Projeto para Flexibilizar o Uso de Anotações”, da edição anterior, traz um quadro sobre
(mr.alexmc@gmail.com): as conferências PLoP, especializadas em padrões.
é engenheiro da Computação, formado pela
Pontifícia Universidade Católica do Rio de Janeiro Neste artigo, primeiramente faremos um breve resumo sobre Generics, enumerando alguns de
(PUC-Rio). Trabalha como analista de Sistemas no seus principais recursos. Em seguida, demonstraremos como utilizar Generics na implementação
TecGraf/PUC-Rio.
dos padrões Abstract Factory, Visitor, Data Access Object, Command e Interpreter. Para cada pa-
drão sendo discutido, será apresentado um problema simples que pode ser resolvido mediante
a aplicação do padrão em questão. Logo em seguida, serão exibidas duas possibilidades de
implementação do padrão considerado para resolver o problema dado, sendo a primeira uma im-
plementação tradicional (sem Generics) e a segunda uma implementação com o uso de Generics.
Por fim, faremos algumas considerações finais concernentes às vantagens e desvantagens em se
aplicar Generics na implementação de padrões de projeto.

50 www.mundoj.com.br
Generics in a nutshell
Generics, ou tipos genéricos, são uma funcionalidade introduzida na ver- diz-se que um tipo é covariante quando este tipo varia de acordo com
são 5 da linguagem Java. Seu emprego promove um alto grau de reusa- outro tipo. Arrays são covariantes. O tipo String é um subtipo de Object
bilidade de código, sem que seja perdida a verificação de tipos em tempo F DPNPBSSBZTTÍPDPWBSJBOUFT 4USJOH<>ÏVNTVCUJQP0CKFDU<>"JOWBSJÉO-
de compilação. Um excelente exemplo do uso de Generics é a premiada cia, ao contrário, é quando um tipo não muda de acordo com variações
API Collections de Java, que abrange os tipos java.util.List, java.util.Stack, em outro tipo. Tipos genéricos são invariantes, e isso quer dizer que
java.util.HashMap, dentre outras classes e interfaces. TipoGenerico<String> não é subtipo de TipoGenerico<Object>, apesar
de String ser um subtipo de Object.
Para compreender os padrões a serem apresentados mais adiante, é
importante que o leitor já possua certo conhecimento de Generics, além
de um conhecimento básico de padrões de projeto. Nesta seção, faremos
Curingas
um “super-resumo” de Generics para ambientar o leitor no tema. De De maneira a flexibilizar o uso de tipos genéricos, devido ao fato de
qualquer forma, recomendamos fortemente a leitura do artigo “Generics eles serem invariantes, utilizamos os chamados curingas (wildcards). Os
Desmistificados”, da edição 34 da Mundoj, para uma abordagem mais curingas são utilizados nas restrições sobre tipos genéricos e denotam-
completa do assunto. se pelo símbolo “?”. Sua sintaxe básica é “TipoGenerico<? extends Clas-
seOuInterface>” e “TipoGenerico<? super ClasseOuInterface>”. Usamos
Sintaxe curingas em conjunto com a palavra-chave extends para denotar uma
restrição “a própria classe ou uma subclasse de” e super para indicar “a
Uma classe ou interface é tornada genérica através do uso de parâmetros própria classe ou uma superclasse de”.
de tipo, declarados entre os sinais de “<” e “>” e separados por vírgulas (se
houver mais de um) após o nome do tipo. Algo como ClasseOuInterface<T, Grosso modo, quando utilizamos a palavra-chave extends com um curin-
E>, em que T e E são os tipos genéricos. Por convenção, são usadas letras ga em um contêiner genérico, somente podemos retirar ou consumir
maiúsculas para denotar os parâmetros de tipo. Para instanciar um tipo elementos (que sejam subtipos do argumento de tipo) do tipo genérico,
genérico, é necessário fornecer valores para os parâmetros de tipo. Estes entretanto não conseguimos fornecer um novo elemento (a exceção é o
valores são conhecidos como argumentos de tipo ou parâmetros reais de literal null). Quando empregamos a palavra-chave super com o mesmo
tipo. Uma referência válida para o tipo genérico declarado anteriormente contêiner genérico, podemos fornecer elementos (de um supertipo do
seria “ClasseOuInterface<Integer, String> instancia;”, em que Integer e argumento de tipo) a ele. Essas situações são exemplificadas nas Lista-
String, nesse caso, são os argumentos de tipo. gens 1 e 2.

Dependendo do tipo sendo declarado, pode fazer sentido expressar res-


Listagem 1. Uso de curingas com extends.
trições sobre os argumentos de tipo válidos para um parâmetro de tipo.
Para declarar restrições que denotem “somente um subtipo deste tipo ou List<Integer> coordenadasDoSalto = Arrays.asList(new Integer[]{1123, 6536,
o próprio tipo”, é utilizada a palavra reservada extends. Como exemplo, 5321});
imagine uma interface para descrever a abstração “operador matemático List<? extends Integer> listaCuringaExtends = coordenadasDoSalto;
binário”, utilizando Generics. Imagine também que esta abstração só está int segundaCoordenada = listaCuringaExtends.get(1); //OK: 6536
listaCuringaExtends.add(0x17); //Erro
definida (só faz sentido) para números. Como poderíamos expressar tal
listaCuringaExtends.add(null); //OK
interface utilizando Generics? OperadorMatematicoBinario<E extends
Number> seria um bom exemplo, pois somente poderão ser utilizados
subtipos de Number como parâmetros reais deste tipo, como desejado
Listagem 2. Uso de curingas com super.
(note que Number é um subtipo dele mesmo, pelo princípio de Substi-
tuição de Liskov – para mais informações, veja a edição 34 da Mundoj). List<? super Integer> listaCuringaSuper = new ArrayList<Integer>();
listaCuringaSuper.addAll(Arrays.asList(new Integer[]{1, 1, 2, 3, 5, 8, 13})); //
OK
Covariância versus Invariância listaCuringaSuper.add(21); //OK
listaCuringaSuper.add(34); //OK
Outro ponto a ser observado é que tipos genéricos são invariantes. Mas
Number sextoElemento = (Number) listaCuringaSuper.get(5); //OK: 8
o que exatamente isso quer dizer? Observemos primeiro a covariância:

51
.VOEP00t1BESÜFTEF1SPKFUPDPN(FOFSJDT

Métodos genéricos intenção do padrão, um problema que motive a aplicação do padrão, um tre-
cho de código que implementa o padrão sem utilizar Generics, um trecho de
Também podemos especificar parâmetros de tipo em métodos, tornando-os código que implementa o padrão usando Generics e algumas consequências
“métodos genéricos”. A sintaxe é similar à dos tipos genéricos, contudo a de- do emprego de Generics na implementação considerada. Os exemplos foram
claração do parâmetro de tipo deve ficar entre os modificadores do método mantidos bem simples para ilustrar de maneira didática cada padrão.
(public, private, static, final, strictfp etc.) e seu tipo de retorno. Deve-se ainda
levar em consideração que não é permitido o uso de curingas em parâmetros Abstract Factory
de tipo de métodos genéricos. Exemplos de métodos genéricos são encontra-
dos na Listagem 3. No exemplo, podemos observar o uso de tipos genéricos Intenção: fornecer uma maneira de criar objetos cujas classes sejam
com métodos estáticos e não-estáticos (métodos agrupar e registrarEvento, inter-relacionadas, sem que os clientes tenham que conhecer as respec-
respectivamente). Também é demonstrado, no método denominado maior, o tivas classes concretas.
uso de tipos genéricos como tipo de retorno e a aplicação de restrições sobre
parâmetros de tipo genérico (no caso para limitar os argumentos de tipo ge- Problema: a Organização Internacional Sem Crise Mundial (OISCM) dese-
nérico a tipos numéricos). ja implementar um simulador geopolítico, de modo a prever a maneira
com que o poder político afeta a economia global. Fomos incumbidos de
modelar a estrutura política das nações, de modo a disponibilizar uma
Listagem 3. Exemplos de métodos genéricos. maneira uniforme de criar elementos políticos das mesmas. Todas as
nações possuem as seguintes abstrações:
public class ExemploMetodosGenericos {
public <T> void registrarEvento(T evento) { t DJEBEÍP
// registrar o evento aqui... t QSFTJEFOUF RVFPCSJHBUPSJBNFOUFÏVNDJEBEÍPEPQBÓT
} t VOJEBEFGFEFSBUJWB DPNPVNFTUBEP VNBQSPWÓODJBPVVNEJTUSJ-
to.
public static <E> String agrupar(Collection<E> elementos) {
StringBuilder sb = new StringBuilder(); Em nosso contexto, a nação é o elemento central na qual as outras
// Agrupar elementos aqui... abstrações estão inseridas. Por isso, ela será utilizada como fábrica. De
return sb.toString();
maneira ilustrativa, também estaremos interessados na área, em km2, e
}
na população de cada unidade federativa.
public <T extends Number> T maior(T a, T b) {
if (a.doubleValue() > b.doubleValue()) { Existem algumas maneiras de se implementar o padrão Abstract Fac-
return a; tory, duas delas são a utilização do padrão Factory Method (método de
} else { fábrica) e do padrão Prototype (protótipo – o que poderia ser feito em
return b; Java utilizando-se reflexão; para um exemplo, conferir o artigo “Padrões
} de Projeto e Reflexão”, da edição 22 da Mundoj). Neste exemplo, empre-
}
gamos a abordagem mais comum, que consiste em utilizar métodos de
public static void main(String[] args) { fábrica para implementar o padrão Abstract Factory.
ExemploMetodosGenericos exemplo = new ExemploMetodosGenericos();
A Listagem 4 contém a implementação das abstrações modeladas. A fá-
exemplo.registrarEvento(“evento 1”); brica abstrata é representada pela interface Nacao, que possui métodos
exemplo.<String> registrarEvento(“evento 2”); de fábrica para os outros tipos.

List<Number> numeros = new ArrayList<Number>();


numeros.add(42); Listagem 4. Interfaces dos elementos políticos.
numeros.add(3.141592);
public interface Nacao {
String s1 = ExemploMetodosGenericos.agrupar(numeros); public Presidente criarPresidente();
String s2 = ExemploMetodosGenericos.<Number> agrupar(numeros); public Cidadao criarCidadao();
public Collection<UnidadeFederativa> criarUnidadesFederativas();
Integer i1 = exemplo.maior(10, 30); //T inferido como Integer }
Double i2 = exemplo.maior(191.1, 0.9); //T inferido como Double
public interface Cidadao {
// atributos e comportamentos de um cidadão.
//Na chamada abaixo, T é inferido como
}
//sendo “Number & Comparable<T>”
Number i3 = exemplo.maior(1234, 2.78); public interface Presidente extends Cidadao {
} // atributos e comportamentos de um presidente.
} }

public interface UnidadeFederativa {


Padrões de projeto com Generics public double getArea();
public long getPopulacao();
Nesta seção, apresentamos exemplos de implementações com Generics para // outros atributos e comportamentos de uma unidade federativa.
}
diversos padrões de projeto. Para cada padrão de projeto, descrevemos a

52 www.mundoj.com.br
.VOEP00t1BESÜFTEF1SPKFUPDPN(FOFSJDT

Para instanciar o padrão, foram implementados os conjuntos de classes


Listagem 6. Problema com a fábrica de elementos políticos.
para a nação Brasil, conforme ilustrado na Listagem 5. As demais nações
podem ser implementadas de maneira análoga, ou seja, implementando public class NacaoMisturada implements Nacao {
a interface "Nacao", que deve construir seus cidadãos, presidentes e uni-
dades federativas específicos. public Cidadao criarCidadao() {
return new CidadaoBrasileiro();
}
Listagem 5. Fábrica de elementos políticos brasileiros e seus tipos concretos.
public Presidente criarPresidente() {
public class Brasil implements Nacao { return new PresidentePortugues();
}
public Cidadao criarCidadao() {
return new CidadaoBrasileiro(); public Collection<UnidadeFederativa> criarUnidadesFederativas() {
} return Arrays.asList(new RioDeJaneiro(), new Lisboa(), new SaoPaulo(),
new Porto());
public Presidente criarPresidente() { }
return new PresidenteBrasileiro(); }
}

public Collection<UnidadeFederativa> criarUnidadesFederativas() {


Para sanar o problema encontrado na implementação anterior, as abs-
return Arrays.asList(new RioDeJaneiro(), new SaoPaulo(), /* ...demais
estados brasileiros ...*/); trações foram tornadas tipos genéricos. O novo modelo para o domínio
} QPEFTFSPCTFSWBEPOB-JTUBHFN"JOUFSGBDF/BDBPBHPSBQPTTVJVN
} parâmetro de tipo genérico recursivo que obriga seus implementadores
a especificarem um tipo genérico que seja implementador da própria
public class CidadaoBrasileiro implements Cidadao { interface (e seja o próprio tipo). Isso faz com que os métodos por ela
// implementação de um cidadão brasileiro.
}
implementados retornem tipos parametrizados com a própria fábrica,
garantindo que somente serão fabricados objetos parametrizados com
public class PresidenteBrasileiro extends CidadaoBrasileiro implements a mesma fábrica (fazendo parte de um mesmo conjunto). A implemen-
Presidente { tação genérica concreta para o Brasil da fábrica de nações e dos tipos
// implementação de um presidente do brasil. concretos fabricados pode ser visualizada na Listagem 8
}

public class RioDeJaneiro implements UnidadeFederativa { Listagem 7. Interfaces genéricas dos elementos políticos.

public double obterArea() { public interface Nacao<T extends Nacao<T>> {


return 43696.054; public Presidente<T> criarPresidente();
} public Cidadao<T> criarCidadao();
public Collection<UnidadeFederativa<T>> criarUnidadesFederativas();
public long obterPopulacao() { }
return 15406478;
} public interface Cidadao<T> {
} // atributos e comportamentos de um cidadão.
}
public class SaoPaulo implements UnidadeFederativa {
// ... código similar ao exibido para a classe RioDeJaneiro, com dados de public interface Presidente<T> extends Cidadao<T> {
// São Paulo … // atributos e comportamentos de um presidente.
} }

public interface UnidadeFederativa<T> {


Parece que o padrão Abstract Factory está cumprindo seu papel, pois public double obterArea();
podemos instanciar conjuntos de classes de maneira disjunta, através public long obterPopulacao();
das fábricas específicas de cada nação (ex.: fábrica Brasil implementada).
// outros atributos e comportamentos de uma unidade federativa.
Se o código cliente será controlado, esta implementação do padrão pode }
até ser satisfatória. Se o propósito for disponibilizá-lo em uma biblioteca,
contudo, você deve estar ciente de que é possível que alguém implemen-
te algo como o contido na Listagem 6. Nela, os tipos de classes criadas A tentativa de criar outra fábrica abstrata que forneça objetos de “conjun-
pela fábrica foram misturados, apesar de não pretendermos que essas tos diferentes” agora é frustrada pelo sistema de tipos, pois misturá-los
classes fossem utilizadas em conjunto. Esse é um problema geralmente resulta em erros de compilação (e não em problemas semânticos, de
encontrado quando temos hierarquias de classes paralelas (no exemplo, violação das restrições do domínio ou erros de runtime). Sempre que
as classes que implementam Cidadao, Presidente e UnidadeFederativa identificarmos códigos com hierarquias de classes paralelas em Java,
são exemplos de hierarquias paralelas). Sendo assim, o que fazer para provavelmente teremos uma oportunidade para empregar a técnica
que essa restrição no uso dos tipos seja garantida? Tipos genéricos ao descrita nesta seção para capturar as relações existentes utilizando tipos
resgate! genéricos.

53
.VOEP00t1BESÜFTEF1SPKFUPDPN(FOFSJDT

Por simplicidade, não nos preocuparemos com a distribuição espacial das


Listagem 8. Fábrica genérica de elementos políticos brasileiros e seus tipos
células nem com sua unicidade dentro da planilha.
concretos.

public class Brasil implements Nacao<Brasil> { Implementamos primeiro os papéis de célula e visitante, como interfaces (vide
-JTUBHFN
"PQBQFMEFWJTJUBOUFÏBUSJCVÓEPPDPNQPSUBNFOUPEFWJTJUBSVNB
public Cidadao<Brasil> criarCidadao() { célula e obter o resultado de seus trabalhos de visita. Cada implementação de
return new CidadaoBrasileiro(); visitante irá definir o que retornar como resultado, portanto a interface declara
}
o retorno de obterResultado() como sendo do tipo Object. Deste modo, cabe
public Presidente<Brasil> criarPresidente() { ao cliente conhecer o tipo real de retorno do visitante utilizado, de maneira
return new PresidenteBrasileiro(); que possa realizar o cast adequado. A célula, bastante simplificada, somente
} consegue ser visitada e retornar seu conteúdo.
public Collection<UnidadeFederativa<Brasil>> criarUnidadesFederativas()
{ Listagem 9. Abstração de uma célula e sua implementação padrão.
Collection<UnidadeFederativa<Brasil>> estadosBrasileiros =
new ArrayList<UnidadeFederativa<Brasil>>(); public interface Celula {
estadosBrasileiros.add(new RioDeJaneiro()); public interface Visitor {
estadosBrasileiros.add(new SaoPaulo()); public void visitar(Celula celula);
// ... adição dos demais estados brasileiros ... public Object obterResultado();
return estadosBrasileiros; }
}
} public void aceitar(Visitor visitante);
public Object obterConteudo();
public class CidadaoBrasileiro implements Cidadao<Brasil> { }
// implementação de um cidadão brasileiro.
} public class CelulaPadrao implements Celula {
private Object conteudo;
public class PresidenteBrasileiro extends CidadaoBrasileiro implements
Presidente<Brasil> { public CelulaPadrao(Object conteudo) {
// implementação de um presidente do brasil. this.conteudo = conteudo;
} }
public class RioDeJaneiro implements UnidadeFederativa<Brasil> { public void aceitar(Visitor visitante) {
// código semelhante ao da implementação anterior visitante.visitar(this);
} }
public class SaoPaulo implements UnidadeFederativa<Brasil> { public Object obterConteudo() {
// código semelhante ao da implementação anterior return conteudo;
} }
}

Visitor
A planilha é vista pelos visitantes como uma coleção de células e deve
Intenção: separar a definição de uma estrutura de dados dos algoritmos que prever as restrições sobre o tipo do conteúdo das células aceitas, confor-
nela operam, permitindo que novos algoritmos sejam criados sem que a estru- me ressaltado na discussão sobre a modelagem. Na Listagem 10 temos a
tura de dados precise ser alterada. implementação da planilha. Para obedecer ao requisito de restrição das
classes de conteúdo das células, sempre que há a tentativa de inserção
Problema: de maneira a ilustrar o padrão Visitor (visitante), imagine que nossos de uma célula na planilha, é feita uma verificação para saber se existe
esforços foram requeridos para auxiliar na elaboração de um aplicativo de pla- BMHVNBSFTUSJÎÍPTPCSFBTDMBTTFTQFSNJUJEBT DMBTTF1FSNJUJEBOVMM
F 
nilha eletrônica. Uma das metas do projeto é permitir que terceiros consigam existindo, é verificada a classe do conteúdo da célula sendo inserida, para
fornecer plug-ins de processamento para a planilha. Nosso objetivo é separar saber se é compatível com a restrição. Caso não seja, uma exceção é lan-
a definição da planilha dos algoritmos que nela operam, pois muitos deles só çada. Note que este problema só será detectado em runtime (tempo de
serão definidos posteriormente. execução). A planilha também colabora com o padrão visitor, podendo
ser visitada por um visitante de células.
A unidade fundamental da planilha será a célula, e a planilha será vista pelos
algoritmos como uma coleção de células. Cada célula terá um conteúdo de- Dois visitantes são fornecidos com suas implementações exibidas na Lis-
finido por uma classe específica, e células cujos conteúdos sejam de classes tagem 11. A classe VisitanteDeImpressao visita as células, armazenando
diferentes poderão perfazer uma mesma planilha, a critério do cliente. Ou seja, PSFTVMUBEPEFUP4USJOH
EFDBEBVNBEFMBTFNVNCVõFS"PmOBMEBTWJ-
uma planilha poderá: sitas, pode-se obter uma string que represente as células visitadas. Como
t DPOUFS TPNFOUF DÏMVMBT DVKP DPOUFÞEP TFKB EF VNB NFTNB DMBTTF PV o método toString(), chamado no conteúdo das células, é herdado da
subclasse; classe Object, nenhuma restrição é feita nas classes que ele pode visitar.
ou
t DPOUFSDÏMVMBTDVKPDPOUFÞEPTFKBIFUFSPHÐOFP PVTFKB DPOUFSDÏMVMBT O segundo visitante, VisitanteDeSoma, acumula o somatório do conte-
textuais, células numéricas, células que contenham uma expressão etc. údo de todas as células numéricas que tenha visitado. Por só trabalhar

54 www.mundoj.com.br
.VOEP00t1BESÜFTEF1SPKFUPDPN(FOFSJDT

Listagem 10. Implementação da planilha. Listagem 11. Visitantes.

public class Planilha { public class VisitanteDeImpressao implements Visitor {


private Collection<Celula> planilha = new ArrayList<Celula>(); private int numeroVisitas = 0;
private Class classePermitida; private StringBuilder buffer = new StringBuilder();
public void visitar(Celula celula) {
public Planilha() { if (numeroVisitas > 0) {
classePermitida = null; buffer.append(“, “);
} }

public Planilha(Class classePermitida) { buffer.append(celula.obterConteudo().toString());


this.classePermitida = classePermitida; ++numeroVisitas;
} }
public String obterResultado() {
public void adicionar(Celula celula){
return buffer.toString();
if (classePermitida != null &&
}
(celula.obterConteudo() == null ||
}
!classePermitida.isAssignableFrom(
celula.obterConteudo().getClass()))) {
public class VisitanteDeSoma implements Visitor {
throw new IllegalArgumentException(
“O conteúdo da célula não é compatível “ + private double acumulador = 0.0;
“com esta instância de planilha.”); public void visitar(Celula celula) {
} Object conteudo = celula.obterConteudo();
planilha.add(celula);
} if (conteudo instanceof Number) {
Number numero = (Number) conteudo;
public Object aceitar(Celula.Visitor visitante) { acumulador += numero.doubleValue();
for (Celula celula : planilha) { }
celula.aceitar(visitante); }
} public double obterResultado() {
return visitante.obterResultado(); return acumulador;
} }
} }

com células numéricas, é feita uma crítica quanto à classe do conteúdo


Listagem 12. Abstração de uma célula tornada genérica e sua implementação
da célula: somente são relevantes subclasses de Number. O resultado é
padrão.
retornado como um Double.
public interface Celula<E> {
Notamos alguns problemas nesta implementação: public interface Visitor<R> {
t B DSÓUJDBRVBOUPBDMBTTFEPDPOUFÞEPEBDÏMVMB BPTFSJOTFSJEBOB public void visitar(Celula<?> celula);
planilha, só é feita em runtime; public R obterResultado();
t BPVUJMJ[BSVNWJTJUBOUF EFWFNPTTBCFSBDMBTTFDPSSFUBEFTFVPCKFUP }
de retorno e inserir um cast; public <R> void aceitar(Visitor<R> visitante);
t BPPCUFSVNBDÏMVMB OÍPTBCFNPTPUJQPEFTFVDPOUFÞEP public E obterConteudo();
}
Para tentar sanar os problemas encontrados na implementação anterior, é
proposta uma nova implementação, agora usando Generics. A Listagem 12 public class CelulaPadrao<E> implements Celula<E> {
apresenta a nova abstração para os papéis de célula e de visitante. Vemos private E conteudo;
que o conteúdo da célula é um parâmetro de tipo e que o tipo de retorno
public CelulaPadrao(E conteudo) {
do visitor é também um parâmetro de tipo. Fazendo assim, agora temos
this.conteudo = conteudo;
como saber o tipo do conteúdo da célula e o tipo de resultado do visitor.
}
O curinga utilizado no tipo Celula<?>, recebido no método visitar(), é public <R> void aceitar(Celula.Visitor<R> visitante) {
necessário porque a planilha pode conter células de tipos diferentes en- visitante.visitar(this);
}
tre si (conforme a modelagem) e precisamos ser capazes de visitar estas
public E obterConteudo() {
planilhas. Se este não fosse o caso, poderíamos ter utilizado um parâmetro
return conteudo;
de tipo para também garantir que os visitantes só fossem utilizados com
}
células de um tipo mais restrito (por exemplo, planilhas cujas células fos- }
sem todas numéricas).

55
.VOEP00t1BESÜFTEF1SPKFUPDPN(FOFSJDT

Outra vantagem do uso de Generics nessa implementação pode ser vista Data Access Object
na Listagem 13. Percebemos que não só o código ficou mais claro e inteli-
gível, como também a tentativa de inserção de uma célula cuja classe não Intenção: abstrair e encapsular todos os acessos a uma fonte de dados. O
seja permitida é capturada em tempo de compilação. A desvantagem é DAO gerencia a conexão com a fonte de dados (que pode ser um banco
que, se não houver nenhuma restrição sobre o conteúdo das células de dados relacional, um arquivo etc.) para obter e armazenar dados.
sendo utilizadas na planilha, ainda assim o cliente deverá fornecer uma
(no caso, a classe Object) ou trabalhar com o tipo bruto. Poderíamos usar Problema: desejamos construir um sistema de software para a loja
“new Planilha<Object>()” para a versão que aceite qualquer tipo de célu- Amparo & Ampère. A loja possui um catálogo com eletrodomésticos,
la e “new Planilha<Number>()” para aceitar somente células numéricas. eletrônicos etc., os quais podem ser consultados, alterados, cadastrados
e removidos. Deseja-se que a aplicação seja independente da tecnologia
de acesso aos dados, pois, num futuro próximo, a Amparo & Ampère
Listagem 13. Implementação da planilha tornada genérica.
vislumbra entrar no mundo SOA e também usar webservices de terceiros
public class Planilha<E> {
como fontes de dados para sua aplicação.
private Collection<Celula<? extends E>> planilha =
new ArrayList<Celula<? extends E>>(); Para que a aplicação fique isolada da tecnologia de acesso e persistência
dos dados, podemos empregar o padrão DAO para fornecer as operações
public void adicionar(Celula<? extends E> celula) { CRUD básicas: criação (C), recuperação (R), atualização (U) e remoção (D).
planilha.add(celula); Supondo que tenhamos as classes Eletronico e Eletrodomestico, teríamos
}
tradicionalmente os DAOs EletronicoDao e EletrodomesticoDao, cujas
public <R> R aceitar(Celula.Visitor<R> visitante) {
for (Celula<?> celula : planilha) { interfaces aparecem na Listagem 15. Codificamos os DAOs com base
celula.aceitar(visitante); em interfaces, seguindo a boa prática OO de programar para interface
} e não para implementação, o que nos permite, por exemplo, substituir
return visitante.obterResultado(); a implementação por mocks para escrita de testes de unidade (na nossa
}
implementação, optamos por utilizar a tecnologia de persistência padrão
}
do Java, a Java Persistence API).

Os visitantes tornados genéricos, exibidos na Listagem 14, têm como A Listagem 16 traz a implementação do DAO de eletrônicos (os demais
principal vantagem a presença do tipo real de retorno no método DAOs possuem implementação semelhante).
obterResultado(), mas ainda continuam tendo que criticar a classe do
conteúdo da célula para realizar seu trabalho. Listagem 15. Interfaces dos DAOs implementados sem Generics.

Listagem 14. Visitantes implementados com Generics. public interface EletronicoDao {


Long salvar(Eletronico eletronico);
Eletronico recuperar(Long id);
public class VisitanteDeImpressao implements Visitor<String> {
void atualizar(Eletronico eletronico);
int numeroVisitas = 0;
void remover(Eletronico eletronico);
StringBuilder buffer = new StringBuilder();
}
public void visitar(Celula<?> celula) {
if (numeroVisitas > 0) { public interface EletrodomesticoDao {
buffer.append(“, “); Long salvar(Eletrodomestico eletrodomestico);
} Eletrodomestico recuperar(Long id);
void atualizar(Eletrodomestico eletrodomestico);
buffer.append(celula.obterConteudo().toString()); void remover(Eletrodomestico eletrodomestico);
++numeroVisitas; }
}
public String obterResultado() {
return buffer.toString();
} Listagem 16. Implementação do DAO de eletrônicos.
}
public class EletronicoDaoImpl implements EletronicoDao {
public class VisitanteDeSoma implements Visitor<Double> { private EntityManager entityManager;
double acumulador = 0.0;
public void visitar(Celula<?> celula) {
public EletronicoDaoImpl(EntityManager entityManager) {
Object conteudo = celula.obterConteudo();
if (conteudo instanceof Number) { this.entityManager = entityManager;
Number conteudoNumerico = (Number) conteudo; }
acumulador += conteudoNumerico.doubleValue();
} public Long salvar(Eletronico eletronico) {
} entityManager.persist(eletronico);
public Double obterResultado() { }
return acumulador;
} public Eletronico recuperar(Long id) {
} return entityManager.find(Eletronico.class, id);

56 www.mundoj.com.br
.VOEP00t1BESÜFTEF1SPKFUPDPN(FOFSJDT

} Dessa forma, enquanto precisarmos apenas do CRUD básico, podemos


utilizar o DAO genérico como DAO para todas as nossas classes. Para
public void atualizar(Eletronico eletronico) { eletrônicos, por exemplo, poderíamos instanciar um DAO da seguinte
entityManager.merge(eletronico); maneira:
}

public void remover(Eletronico eletronico) {


entityManager.remove(eletronico); %BP(FOFSJDP&MFUSPOJDPEBPOFX%BP(FOFSJDP*NQM
}
}
Eletronico>(Eletronico.class, entityManager);

O código das Listagens 15 e 16 pode ser contrastado com o código do


$PNP%"0HFOÏSJDP DPOTFHVJNPTTFHVJSPCPNQSJODÓQJP%3: EPOUSF-
%"0HFOÏSJDPFYJCJEPOB-JTUBHFN PRVBMGB[VTPEF(FOFSJDT/FTUB
peat yourself ou não seja redundante), evitando assim código repetitivo.
implementação do DAO genérico, abstraímos o tipo da entidade pela
qual o DAO é responsável num parâmetro de tipo T, o qual é utilizado
em todos os métodos do DAO, conforme pode ser visto pela interface Command
DaoGenerico<T> mostrada na listagem. A implementação dessa inter-
face, DaoGenericoImpl<T>, também declara o parâmetro de tipo T, e Intenção: encapsular uma solicitação como um objeto, permitindo a
implementa os métodos da interface com base no tipo T. Um detalhe parametrizacão de clientes com diferentes solicitações.
importante é que também precisamos de uma referência para a classe Problema: como programadores da empresa SoftBom, precisamos
específica (objeto do tipo Class) das entidades do DAO, pois o método construir a base de uma estrutura flexível para execução de comandos
find() do EntityManager, utilizado na implementação do método recu- simples, necessária para endereçar requisitos da aplicação que estamos
perar(), precisa saber qual é a classe das entidades a serem obtidas do construindo.
banco de dados, para que ele possa buscar na tabela associada. O leitor
pode estar se perguntando: “mas não poderíamos utilizar o parâmetro O padrão Command é útil para tratarmos solicitações como objetos
de tipo T para isso?”. Devemos lembrar que Generics são implementados de primeira classe. Outra maneira de ser ver esse padrão é enxergá-lo
pelo processo de erasure, o que, em poucas palavras, significa que não como uma substituição orientada a objetos para callbacks de linguagens
podemos obter o tipo real associado ao parâmetro de tipo T. procedurais.

A Listagem 18 mostra a implementação da estrutura flexível de coman-


Listagem 17. Interface e implementação do DAO genérico. EPT  TFN P VTP EF (FOFSJDT FORVBOUP B -JTUBHFN  USB[ VNB JNQMF-
mentação equivalente com o uso de Generics. Sem Generics, não nos é
public interface DaoGenerico<T> { possível lidar com o tipo de retorno das ações que podem ser executadas
Long salvar(T obj); pelos receptores, o que nos força a trabalhar com Object e, consequente-
T recuperar(Long id); mente, termos casts em código cliente. A versão com Generics contorna
void atualizar(T obj);
void remover(T obj); essa limitação por meio da parametrização do Receptor, que é explorada
} na implementação do comando. Com os parâmetros de tipo, a classe
ComandoSimples genérica consegue vincular o tipo de retorno do seu
public class DaoGenericoImpl<T> implements DaoGenerico<T> { método executar() com o tipo da classe do receptor passada como pa-
private EntityManager entityManager;
private Class<T> tipo;
râmetro. Poderíamos ter levado o exemplo a um nível ainda maior de
flexibilidade, adicionando parâmetros ao método executarAcao() do
public DaoGenericoImpl(Class<T> tipo, EntityManager entityManager) { receptor, juntamente com parâmetros de tipo.
this.entityManager = entityManager;
this.tipo = tipo;
} Listagem 18. Implementação de estrutura flexível para comandos sem Generics.

public Long salvar(T obj) { public interface Receptor {


entityManager.persist(obj);
public Object executarAcao();
}
}
public T recuperar(Long id) {
return entityManager.find(tipo, id); public class ComandoSimples {
}
private Receptor receptor;
public void atualizar(T obj) {
entityManager.merge(obj); public ComandoSimples(Receptor receptor) {
} this.receptor = receptor;
}
public void remover(T obj) {
entityManager.remove(obj); public Object executar() {
} return receptor.executarAcao();
} }
}

57
.VOEP00t1BESÜFTEF1SPKFUPDPN(FOFSJDT

Listagem 19. Implementação de estrutura flexível para comandos com Generics. Listagem 20. Testes para o interpretador mini-LISP.
public class Testes {
public interface Receptor<T> {
@Test
public T executarAcao();
public void testAtomo() {
assertEquals(3, atomo(3).interpretar());
}
}
@Test
public void testQuote() {
public class ComandoSimples<A extends Receptor<B>, B> { assertEquals(atomo(3), quote(atomo(3)));
}
private A receptor; @Test
public void testCons() {
public ComandoSimples(A receptor) { assertEquals(“(1 (2 3))”, cons(atomo(1), cons(atomo(2), atomo(3)))
.interpretar().toString());
this.receptor = receptor; }
@Test
} public void testCar() {
assertEquals(atomo(1), car(cons(atomo(1), cons(atomo(2), atomo(3)))));
public B executar() { }
@Test
return receptor.executarAcao(); public void testCdr() {
assertEquals(cons(atomo(2), atomo(3)), cdr(cons(atomo(1), cons(
} atomo(2), atomo(3)))));
}
} }

Interpreter

Intenção: definir uma representação para a gramática de uma dada linguagem, Nas implementações exibidas, temos nossa classe abstrata de expressões
juntamente com um interpretador que usa a representação para interpretar SExp (expressões em LISP são chamadas de s-exps), que define o método
sentenças dessa linguagem. abstrato interpretar(). Cada um dos métodos estáticos implementa um
tipo de expressão, retornando um objeto de uma subclasse anônima de
Problema: queremos construir um interpretador básico para algumas expres- SExp que implementa o método interpretar() de acordo com a semântica
sões da nossa linguagem mini-LISP (nosso “dialeto básico” de LISP em Java). do seu respectivo tipo de expressão. Na versão sem Generics, somos
Temos duas estruturas de dados básicas: o átomo e a lista. O átomo representa obrigados a trabalhar com o tipo Object e a fazer os devidos casts, como
VNWBMPSOVNÏSJDPPVBMGBOVNÏSJDP FY APMB AUFTUF FUD
"MJTUBÏVNB é bem evidenciado nas implementações de car() e cdr(). Já na versão com
associação de átomos com outras listas. Grosso modo, uma lista pode ser Generics, parametrizamos a classe abstrata SExp com um tipo T, utilizado
visualizada como sendo um encadeamento de pares, sendo que o elemento no retorno do método interpretar() e flexibilizamos a classe estática ani-
à esquerda representa um elemento da lista e o elemento à direita o restante nhada Lista com os parâmetros de tipo A e B. Além disso, tornamos gené-
da lista. Geralmente são representadas por parênteses, por exemplo, (1 2), (1 ricos os métodos estáticos que fabricam nossas expressões, eliminando a
(3 (4 5))) etc. Nosso miniinterpretador LISP deve oferecer ainda as seguintes necessidade de casts em nosso código, podendo trabalhar diretamente
funções: com os tipos esperados. Mais detalhes sobre esta implementação de
t RVPUFGVOÎÍPJEFOUJEBEF PVTFKB SFUPSOBBQSØQSJBFYQSFTTÍP Interpreter e alguns dos outros padrões discutidos podem ser obtidos
t DPOTDPOTUSØJMJTUBTDPNBTFYQSFTTÜFTGPSOFDJEBT consultando a obra “Java Generics and Collections” (vide referências).
t DBSSFUPSOBPQSJNFJSPFMFNFOUPEBMJTUB
t DESSFUPSOBBMJTUBTFNPQSJNFJSPFMFNFOUP

Para entendermos melhor nosso problema, vamos fazer um pouco de test-first.


Saber mais

A Listagem 20 apresenta alguns testes para definir como desejamos usar nossa A edição 22 da revista Mundoj traz o artigo “Pa-
API (usamos o JUnit 4 como ferramenta). Como uma observação importante,
drões de Projeto e Reflexão”, no qual é mostrado
lembramos ao leitor que cada teste de unidade deve ser específico, ou seja,
testar uma parte bem definida do sistema de cada vez. Para isso, poderíamos como implementar alguns padrões de projeto
ter utilizado mock objects para testar nossos objetos em isolamento, mas GoF usando técnicas de reflexão.
optamos por manter nossas listagens mais simples e compactas (os testes da
Listagem 20 são importantes, mas estariam mais para testes de integração do A edição 34 da revista Mundoj traz o artigo
que de unidade). Para mais dicas sobre testes de unidade e mock objects, ver “Generics Desmistificados”, um artigo bastante
artigo “Testes Unitários para Camadas de Negócios”, da edição 23 da Mundoj.
abrangente descrevendo como utilizar os tipos
Na Listagem 21 trazemos o código do interpretador escrito sem o uso de genéricos da linguagem Java.
Generics e, em seguida, na Listagem 22, o código equivalente com o uso de
Generics.

58 www.mundoj.com.br
.VOEP00t1BESÜFTEF1SPKFUPDPN(FOFSJDT

Listagem 21. Implementação de mini-LISP sem Generics. Listagem 22. Implementação de mini-LISP com Generics.

public abstract class SExp { public abstract class SExp<T> {


abstract public Object interpretar(); abstract public T interpretar();

public boolean equals(Object obj) { public boolean equals(Object obj) {


if (obj instanceof SExp) { SExp<T> exp = (SExp<T>) obj;
SExp exp = (SExp) obj;
return this.interpretar().equals(exp.interpretar());
return this.interpretar().equals(exp.interpretar());
} }
return false;
} static SExp<Integer> atomo(final int i) {
return new SExp<Integer>() {
static SExp atomo(final int i) { public Integer interpretar() {
return new SExp() { return i;
public Integer interpretar() { }
return i; };
} }
};
}
static SExp<String> atomo(final String s) {
static SExp atomo(final String s) { return new SExp<String>() {
return new SExp() { public String interpretar() {
public String interpretar() { return s;
return s; }
} };
}; }
}
static <T> SExp<T> quote(final SExp<T> exp) {
static SExp quote(final SExp exp) {
return exp;
return exp;
} }

static SExp cons(final SExp e1, final SExp e2) { static <A, B> SExp<Lista<A, B>> cons(final SExp<A> e1, final SExp<B> e2) {
return new SExp() { return new SExp<Lista<A, B>>() {
public Lista interpretar() { public Lista<A, B> interpretar() {
return new Lista(e1.interpretar(), e2.interpretar()); return new Lista<A, B>(e1.interpretar(), e2.interpretar());
} }
}; };
} }
static SExp car(final SExp lista) {
return new SExp() { static <A, B> SExp<A> car(final SExp<Lista<A, B>> lista) {
public Object interpretar() { return new SExp<A>() {
return ((Lista) lista.interpretar()).primeiroElemento(); public A interpretar() {
} return lista.interpretar().primeiroElemento();
}; }
} };
}
static SExp cdr(final SExp lista) {
return new SExp() { static <A, B> SExp<B> cdr(final SExp<Lista<A, B>> lista) {
public Object interpretar() {
return new SExp<B>() {
return ((Lista) lista.interpretar()).restante();
} public B interpretar() {
}; return lista.interpretar().restante();
} }
};
private static class Lista { }
private final Object primeiroElemento;
private static class Lista<A, B> {
private final Object restante; private final A primeiroElemento;
private final B restante;
public String toString() {
public String toString() {
return “(“ + primeiroElemento + “ “ + restante + “)”;
} return “(“ + primeiroElemento + “ “ + restante + “)”;
}
// construtor, métodos get() e equals() omitidos... // construtor, métodos get() e equals() omitidos...
} }
} }

59
.VOEP00t1BESÜFTEF1SPKFUPDPN(FOFSJDT

Considerações finais e interfaces quaisquer, quando o tipo Object estiver sendo utilizado como
argumento ou no retorno de métodos, provavelmente teremos uma oportuni-
A noção de padrões de projeto é essencial para o desenvolvedor moderno; dade para utilizar tipos genéricos. Como candidatos, podemos citar os padrões
conhecer os diferentes aspectos que cada escolha de implementação apre- Strategy, State, Visitor e Composite.
senta é de extrema valia. Neste artigo, contrastamos implementações básicas
2) Hierarquia de classes paralelas: padrões que são constituídos por hierarquias
de alguns padrões de projeto com suas possíveis implementações utilizando
de classes paralelas, ou relacionadas, são ótimos alvos para tipos genéricos de
tipos genéricos. Analisamos os prós e os contras de cada abordagem, mos-
Java. Como exemplo, podemos citar os padrões Abstract Factory, Observer,
trando como Generics podem ajudar a transformar erros de execução, ou de
Prototype, Strategy e Bridge.
estruturação do código, em erros de compilação, e qual o trabalho adicional
para tanto. A principal desvantagem de se utilizar Generics na implementação “Mas o nobre projeta coisas nobres e na sua nobreza perseverará” (Is 32:8).
de padrões é a complexidade de implementação, que pode ser bastante alta,
dependendo dos recursos de Generics sendo utilizados.

Em nosso texto, utilizamos como exemplos os padrões Abstract Factory, Visitor, Referências
Data Access Object, Command e Interpreter para ilustrar nossa discussão. Ain-
t +BWB(FOFSJDTBOE$PMMFDUJPOT/BGUBMJO .BVSJDF8BEMFS 1IJMJQ 

da existem dezenas de outros padrões de projeto que podem se beneficiar dos t %FTJHO QBUUFSOT (BNNB  &SJDI )FMN  3JDIBSE +PIOTPO  3BMQI 7MJTTJEFT  +PIO
tipos genéricos da linguagem Java. Como exemplo, podemos citar o padrão 

Fluent Interface para a construção de Domain-Specific Languages. Generics t &õFDUJWF+BWB OEFEJUJPO#MPDL +PTIVB 

t $PSF+&&1BUUFSOT%BUB"DDFTT0CKFDU IUUQKBWBTVODPNCMVFQSJOUTDPSFKFF-
são um ótimo recurso para a definição desse tipo de interface, muito bem QBUUFSOT1BUUFSOT%BUB"DDFTT0CKFDUIUNM
explorado, por exemplo, pelo framework EasyMock, usado para a criação de t 5IF1SBHNBUJD1SPHSBNNFS'SPN+PVSOFZNBOUP.BTUFS)VOU "OESFX5IPNBT 
Mock Objects para facilitar a escrita de testes de unidade. %BWJE 

t 1BSBEBEPTEFOBÎÜFTIUUQQUXJLJQFEJBPSH
Do que nos foi possível expor neste artigo, podemos fornecer duas orientações t 0VUSBTSFGFSÐODJBTFNQBESÜFTEFTPGUXBSF
t 1BUUFSOTPG&OUFSQSJTF"QQMJDBUJPO"SDIJUFDUVSF'PXMFS .BSUJO 

gerais que o leitor poderá usar para tornar um padrão de projeto genérico em t &OUFSQSJTF*OUFHSBUJPO1BUUFSOT%FTJHOJOH #VJMEJOH BOE%FQMPZJOH.FTTBHJOH4P-
Java: MVUJPOT)PIQF (SFHPS8PPMG #PCCZ 

t 1BUUFSO0SJFOUFE 4PGUXBSF "SDIJUFDUVSF 7PMVNF  " 4ZTUFN PG 1BUUFSOT #VTDI-
1) Uso da classe Object: padrões que fazem uso do tipo Object em argumentos NBOO 'SBOLFUBM 

ou retornos de métodos. Não apenas em padrões, mas no projeto de classes

60 www.mundoj.com.br

Você também pode gostar