Escolar Documentos
Profissional Documentos
Cultura Documentos
Padra o Proje To Com Generics
Padra o Proje To Com Generics
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.
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.
52 www.mundoj.com.br
.VOEP00t1BESÜFTEF1SPKFUPDPN(FOFSJDT
public class RioDeJaneiro implements UnidadeFederativa { Listagem 7. Interfaces genéricas dos elementos políticos.
53
.VOEP00t1BESÜFTEF1SPKFUPDPN(FOFSJDT
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
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.
56 www.mundoj.com.br
.VOEP00t1BESÜFTEF1SPKFUPDPN(FOFSJDT
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
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.
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