Você está na página 1de 60

Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Padrões de Projeto: 
Introdução aos Padrões da “Gangue dos Quatro”

Fábio Nogueira de Lucena
fabio@inf.ufg.br

19/11/2007

Instituto de Informática (UFG)

Resumo

O   livro  Design   Patterns:   Elements   of   Reusable   Object­Oriented   Software  foi   escrito   por   Erich 
Gamma, Richard Helm, Ralph Johnson e John Vlissides. Estes autores são conhecidos por “gangue 
dos quatro”, ou pela abreviação da expressão  em inglês GoF (abreviação  amplamente empregada 
neste texto). Este livro é um clássico quando o assunto é padrão de projeto. Editado pela Addison­
Wesley   em   1995,   contém   23   padrões   de   projeto.   Apesar   de   celebridade,   a   leitura   é   difícil, 
principalmente para iniciantes. 
O presente texto não apresenta nenhum conteúdo novo acerca destes 23 padrões. Também não espere 
que tudo apresentado no livro da GoF seja aqui reapresentado. Este texto não dispensa a leitura do 
livro da GoF. Leitores que estão iniciando seus estudos com padrões de projeto, contudo, deverão 
encontrar mais facilidade ao consultar este documento com o acompanhamento do livro da GoF. Esta 
é a pretensão deste texto: legibilidade.
Há várias referências na literatura com propósitos similares que também contribuíram na confecção 
deste  documento.  The Design  Patterns  Java  Companion,  James  W. Cooper,   Adisson­Wesley, 1998   e 
Thinking in Patterns: Problem­Solving Techniques Using Java, Bruce Eckel, MindSet, Inc. Convém citar 
uma terceira fonte, cuja leitura estimula a imaginação: Design Patterns Explained: A New Perspective on  
Object­Oriented Design, Allan Shalloway & James R. Trott, Addison­Wesley, 2002. Por último, Software 
Architecture Design Patterns in Java, Partha Kuchana, Auerbach  Publication, 2004, reapresenta  os 23 
padrões da GoF, dentre outros. É deste último livro a definição de padrão de projeto, fornecida abaixo:
“Um padrão de projeto é uma boa prática documentada ou núcleo de uma solução que foi aplicada 
satisfatoriamente em vários contextos para resolver um problema recorrente em um conjunto
específico de situações.” (Partha Kuchana)

1 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Introdução
Os   padrões   da   GoF   são   classificados   em   padrões   de   criação,   padrões   estruturais   e   padrões   de 
comportamento.   A   distribuição   dos   padrões   por  esta  classificação   é  discutida   abaixo   e  precede  a 
discussão dos padrões de projeto proriamente ditos.
Convém reafirmar que o propósito deste texto é apresentar um acompanhamento para a leitura do 
livro da GoF. Não se tem o propósito de substituir aquele livro por este texto, que pode ser visto como 
um complemento, em vez de substituto. 
Também é importante esclarecer que os 23 padrões discutidos no livro da GoF não são os únicos 
disponíveis  nem   os   “melhores”   padrões   que   existem.   Em  http://hillside.net/  o   leitor   terá  acesso   a 
muitos outros padrões, assim como informações úteis ao emprego de padrões. Em tempo, padrão de 
projeto orientado a objeto é uma proposta de solução para um problema recorrente e, em particular, 
faz uso de orientação a objetos. 
A   atividade  de   projetista   de  software  orientado   a  objetos,   de   tempos  em  tempos,   se   depara  com 
problemas   ou   dificuldades   freqüentes.   Soluções   foram   propostas   e   validadas   para   alguns   destes 
problemas. Com o propósito  de socializar estas soluções e permitir a reutilização do conhecimento já 
experimentado, o par problema e solução foi empacotado no que denominamos de padrão. 
Para empregar um padrão, portanto, é necessário a existência de um problema. Ou seja, o projetista 
deverá ter a habilidade para, dado determinado contexto, identificar a viabilidade ou não do emprego 
de um padrão. Este é um procedimento análogo ao de um médico ao prescrever um medicamento. 
Padrão não é um remédio para todos os males, mas apenas para um em particular. 
O   emprego   da   abordagem   proposta   por   um   padrão   também   exige   trabalho   intelectual.   Embora   a 
estrutura da solução seja parte do padrão, detalhes peculiares ao contexto onde este será empregado 
estarão   presentes   na   solução   e   não   podem   ser   antecipados.   Alterta   aos   leitores:   as   soluções 
apresentadas   não   estão   prontas   para   o   consumo   como   apresentadas,  in   natura.   Será   necessário 
“moldar” a solução para o caso investigado. 
O elemento mais relevante de um padrão é a compreensão dele e não uma ou outra implementação em 
particular. Recorrendo ao paralelo com a medicina, médicos prescrevem medicamentos com base em 
um   princípio   ativo   contido   no   remédio.   É   preciso   conhecer   o   remédio.   Se   esta   compreensão   for 
atingida, então o leitor deverá estar apto a empregar o padrão na sua linguagem de programação de 
preferência, embora aquelas orientadas a objetos como Visual Basic .NET® e Delphi®, por exemplo, 
ofereçam construções desejáveis para esta finalidade. Neste texto, os exemplos são fornecidos em 
Java (http://java.sun.com) e os modelos fazem uso da UML (http://www.uml.org).  
Por último, os nomes dos padrões não foram traduzidos para o português, não há uma justificativa 
razoável. Contudo, saberemos qual o padrão que está sendo discutido.

Padrões de criação
Software orientado a objeto faz uso de objetos. Objetos só existem porque foram criados a partir de 
classes. Em Java, por exemplo, a criação de uma instância da classe  Livro  pode ser realizada pela 
sentença abaixo.
Livro l = new Livro();
Nem sempre esta forma é suficiente. Os padrões de criação, todos eles, mostram como criar instâncias 
em cenários mais elaborados. Estes cenários são abstraídos em padrões de criação. 

2 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

São cinco os padrões de criação: Abstract Factory (8), Builder (11), Factory (3), Prototype (13) e 
Singleton (14).

Padrões estruturais
Objetos são obtidos de classes, que podem ser numerosas e apresentar relacionamento complexo entre 
elas. Padrões estruturais apresentam organizações elegantes onde classes são combinadas para formar 
estruturas maiores. A idéia é oferecer combinações gerenciáveis de classes. 
São sete os padrões estruturais: Adapter (17), Bridge (11), Composite (26), Decorator (28), Façade 
(34), Flyweight (45) e Proxy (35).

Padrões de comportamento
Só existem objetos porque foram criados, talvez com o apoio de padrões de criação. O relacionamento 
entre objetos pode ser sofisticado e capturado em padões estruturais. A interação entre os objetos, ou 
seja, a troca de mensagens entre eles, também pode ser complexa, exigindo que cenários comuns 
possam ser capturados em padrões de comportamento. 
São onze os padrões de comportamento: Chain of responsibility (38), Command (40), Interpreter (37), 
Iterator (42), Mediator (44), Memento (45), Observer (47), State (50), Strategy (53), Template (37) e 
Visitor (56). 

Padrões, frameworks e arquiteturas de


software
Independente de um software possuir uma descrição explícita ou não, todo ele possui uma arquitetura 
de software. Segundo Hofmeister et al., em  Applied Software Architecture, Addison­Wesley, 2000, 
uma arquitetura de software descreves os elementos de um sistema, como eles se encaixam e como 
eles cooperam uns com os outros com o propósito de satisfazer os requisitos do sistema. 
Padrões de projeto são construções “menores” que elementos de arquiteturas de software. Geralmente 
são   empregados   durante   o   detalhamento   do   projeto   e   para   implementar   os   elementos   de   uma 
arquitetura. Arquitetura de software encontra­se em um nível superior, é projeto de mais alto nível. 
Frameworks  também definem questões estruturais de um software, o fluxo de controle e de dados, 
assim   como   uma   arquitetura   de   software.   Contudo,  frameworks  vêm   acompanhados   de 
implementações parciais dos elementos definidos, ao contrário de uma arquitetura de software. De 
acordo com esta definição, que é mais discutida no livro supracitado,  frameworks  são um mixto de 
arquitetura de software e implementação. 

Factory (criação)
Também conhecido por Factory Method, este padrão encapsula a responsabilidade pela criação de um 
objeto   quando   há   opção   de   qual   classe   empregar.   O   Factory   ainda   é   empregado   quando   várias 
atividades estão envolvidas na criação da instância desejada como a definição de valores iniciais, por 
exemplo. Nestes casos, em vez de exigir que a decisão seja do conhecimento de todo e qualquer 
cliente, assim como detalhes da montagem de um objeto válido, tais operações são encapsuladas em 
um Factory (Method). 
Este padrão retorna uma instância de uma classe, dentre um conjunto de classes possíveis. A escolha 
da classe é baseada em informações fornecidas ao padrão. Em geral, todas as classes das quais uma 
instância   pode   ser   gerada   possuem   uma   classe   ancestral   comum   ou   implementam   uma   interface 
comum.

3 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Especialmente quando a instância desejada implementa uma interface I ou é subclasse de uma classe 
C, a identificação da implementação de I ou da subclasse de C a ser empregada, pode envolver lógica 
que não é convenientemente fornecida em um objeto do domínio da aplicação. Por exemplo, a classe 
Impressora,   pelo   princípio   de   projeto   conhecido   por  separation   of   concerns,   não   deveria   ser 
responsável, ela própria, pela identificação da classe específica que trata dos serviços oferecidos por 
determinado tipo de impressora. Esta identificação pode ficar encapsulada em um Factory.
Isto porque para a semântica da aplicação esta decisão pode envolver considerações técnicas e não do 
domínio,   como   no   exemplo   fornecido.   Também   pode   haver   interesse   em   otimizar   a   gerência   de 
memória e, em conseqüência, reutilizar objetos. O código que implementa esta gerência não seria 
adequadamente fornecido em uma classe do domínio da aplicação.
Nestes casos, fazer com que uma classe do domínio realize a decisão é acrescentar a tal classe uma 
responsabilidade alheia ao seu objetivo, o que reduz a coesão desta classe. Em conseqüência, melhor é 
criar uma classe que crie a instância desejada, o Factory, que não faz sentido para o domínio da 
aplicação, mas oferece serviço necessário para a aplicação. Este padrão permite manter interesses 
distintos em classes distintas, o que favorece a coesão das classes resultantes.
A   implementação   do   padrão   Factory   exige   a   criação   de   uma   classe,   geralmente   denominada   de 
Xfactory, onde X é o identificador da classe a partir da qual uma instância deve ser criada. Convém 
ressaltar que X também pode ser uma superclasse da classe a partir da qual a instância desejada será 
criada. Também é possível que X seja uma interface e, neste caso, o resultado será um objeto de uma 
classe que implementa X.
Seguindo esta convenção, a classe AnimalFactory cria instâncias de Animal, seja da classe Animal
propriamente   dita   ou   de   alguma  subclasse  como  Gato  e  Cachorro.   Uma   fábria  de   instâncias   de 
Pessoa  pode   envolver   lógica   considerável   para   validar   a   operação.   Tal   lógica   deverá   estar 
encapsulada   na   classe  PessoaFactory.   Criar   instâncias   de   classes   que   implementam   a   interface 
TrataArquivo é uma atribuição típica de uma classe denominada de TrataArquivoFactory e assim 
por diante. A figura abaixo apresenta os modelos destes exemplos.

AnimalFactory PessoaFactory TrataArquivoFactory

Animal <<Interface>>
Pessoa TrataArquivo

Gato Cachorro LinuxFile WinFile MacOsFile

Implementação
A   implementação   do   padrão   Factory   deve   oferecer   flexibilidade   quanto   à   classe   de   origem   da 
instância a ser criada. Ou seja, a lógica de criação da instância provavelmente irá escolher entre as 
implementações disponíveis de uma interface ou entre as subclasses da classe desejada. Por exemplo, 
dependendo do sistema operacional empregado será criada uma instância de  LinuxFile, WinFile ou 
MacOsFile. A escolha poderá ser determinada por uma estrutura de decisão, por exemplo, if. Neste 
caso, a condição seria responsável por determinar qual o sistema operacional empregado.  
Alternativamente, pode­se obter a informação, a classe a ser empregada pelo Factory, de um arquivo. 
O nome da classe pode ser depositada, por exemplo, na variável  nomeClasse  do tipo  String. Em 
Java, criar uma instância de uma classe através do nome desta é uma tarefa que pode ser realizada 
com a sentença abaixo.

obj = (TrataArquivo) Class.forName(nomeClasse).newInstance();

4 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Para   a   sentença   acima,   assuma   que  nomeClasse  contém   o   identificador   da   classe  LinuxFile, 
WinFile  ou  MacOsFile.   Deste   identificador   é   possível   obter   um   objeto   que   representa   a   classe 
correspondente. A partir deste objeto pode­se criar instâncias via  newInstance(). Observe que o 
retorno é moldado para a interface TrataArquivo e a referência depositada em obj, que deverá ser o 
retorno do método  getTrataArquivo() da classe  TrataArquivoFactory. A figura abaixo ilustra 
este cenário.

TrataArquivoFactory
<<Interface>>
+ getTrataArquivo() : TrataArquivo
TrataArquivo

getTrataArquivo obtém nome da 
classe cuja  instância é criada via LinuxFile WinFile MacOsFile
Class.forName(nome).newInstance().

Observe que não é imposta nenhuma restrição quanto à origem do nome da classe, que pode estar 
armazenado   em   um   arquivo  properties,   pode   ser   obtido   via   a   moderna   opção   de   configuração 
fornecida via Preferences (pacote java.util.prefs), pode ser obtido de um banco de dados, de um 
arquivo de configuração XML ou até mesmo de um URL específico, entre outras.

Exemplo
A pequena aplicação abaixo ilustra o emprego do padrão Factory. A classe  TrataArquivoFactory
contém   o   método  getObjectFromClass(String),   que   obtém   um  objeto   da   classe   cujo   nome   é 
fornecido como argumento e o método getClassName(), que é apenas uma abstração para a escolha 
da classe que deverá ser criada. Conforme comentado anteriormente, esta decisão pode se basear em 
uma lógica complexa, ou pode a classe desejada simplesmente ser fornecida em um arquivo. Neste 
exemplo,   por   diversão,   usou­se   um   sorteio.   Em   um   cenário   mais   realista,   o   sorteio   do   método 
getClassName() seria substituído por uma identificação do sistema operacional em execução.

package factory;

interface TrataArquivo {}

class LinuxFile implements TrataArquivo {}


class WinFile implements TrataArquivo {}
class MacOsFile implements TrataArquivo {}

public class TrataArquivoFactory {

public static void main(String[] args) {


System.out.println(getTrataArquivo().getClass().getName());
}

public static String getClassName() {


int v = (int)(Math.random() * 3);
String str = "factory.";
return str + new String[] { "LinuxFile", "WinFile", "MacOsFile" }[v];
}

// Factory Method
public static TrataArquivo getTrataArquivo() {
return (TrataArquivo) getObjectFromClass(getClassName());
}

public static Object getObjectFromClass(String classe) {


try {
Class c = Class.forName(classe);
Object obj = c.newInstance();
return obj;

5 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

} catch (Exception e) {
return null;
}
}
}

Do código acima convém observar que o nome da classe retornado por  getClassName()  contém 


como prefixo o nome do pacote,  factory, seguido de ponto e só então um dos nomes contidos no 
array. Em tempo, só para relembrar, o nome de uma classe é formado pelo nome do pacote ao qual 
pertence, seguido de ponto, seguido do identificador da classe. A classe String, por exemplo, é parte 
do pacote java.lang. Ou seja, para criarmos uma instância desta classe empregando este método é 
preciso fazer uso de sentença similar àquela abaixo.
obj = (String) Class.forName(“java.lang.String”).newInstance();

Execuções   deste   programa   deverão   produzir,   na   saída   padrão,   os   nomes   das   classes   que   foram 
sorteados e cujas instâncias correspondentes foram empregadas para se obter os nomes impressos. 

Exemplo
Um outro exemplo pode ser fornecido quando a criação de uma instância é realizada por meio de um 
classe armazenada em um array de bytes. Em cenário como este, a classe pode estar disponível em 
um registro de um SGBD, por exemplo, cujo nome não é conhecido senão em tempo de execução, ao 
contrário do exemplo anterior. Para este exemplo, defina Servico a interface que contém operação de 
interesse, conforme abaixo.
package factory;

public interface Servico {


void fazAlgumaCoisa();
}

Para usufruir de uma implementação desta interface é necessário requisitar a operação correspondente, 
conforme abaixo. Observe, contudo, que não é feita nenhuma referência para a classe que implementa 
a interface. Não há nenhuma indicação de quem, de fato, executará a operação desejada. O código 
abaixo limita­se a obter uma instância por meio de  ServicoFactory  (emprego do padrão Factory 
para a interface Servico) e envio da mensagem de interesse. 
package factory;

public class UsufruiServico {


public static void main(String[] args) {
Servico s = ServicoFactory.getServico();
s.fazAlgumaCoisa();
}
}

A execução da operação desejada será fornecida pelo retorno do Factory que, portanto, deve conhecer 
pelo menos uma implementação. O conhecimento de qual classe implementa a interface é fornecido 
por   um   arquivo   de   propriedades.   Para   este   exemplo,   este   arquivo   deve   conter   uma   única   linha, 
conforme abaixo, onde é indicado o nome do arquivo que contém a classe que implementa a interface 
Servico.

servico=ImplementaServico.class

O que se observa acima é a identificação do nome de um arquivo, por meio da chave identifica por 
servico,   que   implementa   a   interface  Servico.   Para   recuperar   este   arquivo   de   propriedades 
(configuracao.properties)   e   obter   o   valor   da   chave  servico  pode­se   fazer   uso   da   classe 
Properties. A implementação do Factory, fornecida abaixo, fornece os detalhes.
package factory;
import java.util.Properties;
public class ServicoFactory extends ClassLoader {
private static ServicoFactory sf = null;

6 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

public static Servico getServico() {


return getServico("configuracao.properties");
}
public static Servico getServico(String origem) {
if (sf == null)
sf = new ServicoFactory();

Servico s = null;
Properties p = new Properties();
try {
p.load(getSystemResourceAsStream(origem));
String cls = p.getProperty("servico");
byte[] bytes = new byte[2048];
int nb = getSystemResourceAsStream(cls).read(bytes);
Class classe = sf.defineClass(null, bytes, 0, nb);
s = (Servico) classe.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return s;
}
}

No código acima, logo após obter o nome do arquivo contendo a classe que implementa a interface 
Servico, um  array  de 2k bytes é criado. Este  array  é criado para armazenar todo o conteúdo do 
arquivo contendo a classe que implementa a interface. Naturalmente este passo não é necessário, mas 
foi   fornecido   para   ilustrar   que,   de   fato,   a  implementação   da  interface  pode   vir,   inclusive,   de   um 
SGBD. Neste caso, uma consulta seria realizada e a seqüência de bytes devidamente obtida. Uma vez 
de posse da  seqüência  de bytes,  o processo  que  segue  é o  mesmo, independente  de como  foram 
obtidos.   Em   particular,   o   método   protegido  defineClass  é   chamado   (isto   justifica   porque   este 
Factory   estende   a   classe  ClassLoader.   A   partir   do   ponto   que   se   tem   uma   instância   de  Class
correspondente à classe de interesse, o passo seguinte é requisitar a criação de uma instância, o que é 
possível por meio da mensagem newInstance().
Embora   o   código   deste   Factory   seja   relativamente   simples,   isto   não   impede   que   seja   ilustrado   o 
processo   de   criação   de   uma   instância   de   forma   “elaborada”,   possivelmente   obtida   de   classe 
armazenada em um SGBD, conforme comentado acima. Em conseqüência, as operações necessárias 
para se criar instâncias assim, conforme ilustrado no código acima, devem estar em uma classe que 
implementa o padrão Factory. 

Exemplo
Um último e simples exemplo é fornecido abaixo. A classe Utils contém o método newDate(), que 
retorna uma instância de java.util.Date conforme o dia, mês e ano fornecidos como argumento. 
public class Utils {
public static Date newDate(int dia, int mes, int ano) {
Calendar c = Calendar.getInstance();
c.set(dia, mes, ano);
return c.getTime();
}
}

Exercícios
Para a solução de cada um dos itens abaixo escreva uma ou mais classes em Java com funcionalidade 
suficiente para implementar todos os requisitos fornecidos.

1. O exemplo acima obtém a classe a ser criada de um sorteio. Em cenário real é bem provável que 
este não seja o caso. Neste exercícios, em vez de um sorteio, faça uso do padrão Factory que 
identifica   qual   a   classe   a   ser   criada   de   um   arquivo   de  propriedades.   A   classe 
java.util.Properties oferece os serviços load e store (argumentos omitidos), que podem ser 

7 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

empregados para carregar o conteúdo de um arquivo de propriedades e, no sentido inverso, para 
gravar as propriedades em um arquivo. Há variantes destes métodos para tratar arquivos XML 
através dos métodos  storeToXML  e  loadFromXML. Para obter o valor de uma propriedade há o 
método  getProperty(String key),   onde  key  representa   o   identificador   da   propriedade.   O 
método  setProperty(String key, String value)  que cria a propriedade  key  com o valor 
value.

2. A   classe  ImpostoDeRenda  possui  o   método  calculo()  que utiliza  um  de  dois  métodos  para 
cálculo   do   imposto   de   renda:   ou   o   simplificado   ou   completo,   implementados   pelas   classes 
Simplificado  e  Completo,   respectivamente.   Estas   duas   classes   são   derivadas   da   classe 
MetodoCalculo,   que   é   abstrata   e   possui   um   único   método   que,   à   semelhança   da   classe 
ImpostoDeRenda, também é calculo(). O método calculo() da classe ImpostoDeRenda deve 
fazer uso de uma implementação de MetodoCalculo, sem exatamente conhecer quem implementa 
o método, ou seja, se a classe  Simplificado  ou a classe  Completo. A decisão de qual método 
empregar   é   definida   em   tempo   de   execução,   quando   uma   instância   da   classe 
ImpostoSimplificado ou da classe ImpostoCompleto, ambas derivadas de ImpostoDeRenda, é 
criada. Código cliente da classe  ImpostoDeRenda  deverá empregar uma instância de uma das 
classes que implementa  ImpostoDeRenda  sem conhecer realmente quem implementa esta classe 
abstrata. A decisão de qual classe utilizar é decidida no método  newImpostoDeRenda() da classe 
ImpostoDeRendaFactory.   Este   método  static  retorna   uma   instância   de  ImpostoDeRenda. 
Estabeleça a decisão de qual classe empregar da forma que considerar apropriado. Observe que a 
instância retornada deverá estar apta a receber a mensagem calculo() cuja operação deverá ser 
consistente com a classe da instância criada.

Abstract Factory (criação)


O padrão Factory (3) cria uma instância de uma classe dentre um conjunto delas. Em alguns casos, 
contudo, estamos interessados em instâncias de um conjunto de classes, onde a escolha não é entre 
classes, mas entre conjuntos de classes e, em conseqüência, quando uma instâncias é desejada, deverá 
ser criada do conjunto apropriado. À semelhança do Factory (3), o Abstract Factory também retorna 
instâncias, mas instâncias de classes que fazem parte de um mesmo conjunto. Quando um conjunto 
deve ser utilizado, então apenas instâncias de classes deste conjunto devem ser produzidas.
Um exemplo comum é a implementação de drivers que oferecem acesso a bancos de dados. Em geral, 
uma  aplicação em Java faz uso de JDBC para ter acesso a informações armazenadas em bancos de 
dados   relacionais,   independente   do   fornecedor   do   banco.   JDBC   é  uma   interface   de   programação 
formada por várias classes e interfaces. Naturalmente, a implementação destas interfaces e classes 
varia conforme o banco de dados empregado. Para o PostgreSQL tem­se uma implementação distinta 
daquela do HSQLDB, por exemplo. Não podemos combinar as classes que implementam JDBC para 
fornecedores distintos. A aplicação deve fazer uso de várias instâncias de classes distintas, mas de um 
mesmo conjunto. 
É o padrão Abstract Factory que encapsula a coordenação necessária para que as instâncias sejam 
criadas a partir de classes de um mesmo conjunto, de tal forma que qualquer que seja a classe que faz 
parte de JDBC utilizada pela aplicação, se esta faz uso do PostgreSQL, então o Abstract Factory irá 
oferecer uma instância de uma classe que implementa o driver para este banco. Se a aplicação faz uso 
do HSQLDB, então o Abstract Factory irá oferecer uma instância de uma classe que implementa o 
driver JDBC para o HSQLDB. 

Implementação
A implementação deste padrão envolve o emprego de várias classes, em um cenário relativamente 
complexo de interação entre as classes. Suponha a existência de uma classe  Data e a classe Moeda. A 
primeira representa uma data qualquer, por exemplo, 7 de setembro de 2004. A outra representa uma 

8 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

quantidade em dinheiro, por exemplo, R$25,00. Em ambos os casos você deve ter observado que a 
forma empregada para exibir a data e a quantia em dinheiro são típicas do Brasil. Para um país de 
língua inglesa outra forma deveria ser utilizada. Ou seja, September 07, 2004 para a data e $25.00 
para a moeda (estou assumindo a paridade de um dólar para um real). 
O que é preciso neste caso, é a implementação das classes Data e Moeda conforme a localidade. Para 
o   Brasil,   por   exemplo,   tem­se   implementações   distintas   daquelas   para   países   de   língua   inglesa, 
conforme a figura abaixo.

Para uma aplicação cliente, o padrão Abstract Factory pode ser empregado para evitar que a aplicação 
use, por exemplo, a implementação para o Brasil da classe  Data e a implementação para um país de 
língua inglesa para a classe  Moeda, misturando data de um com moeda de outro. O que é preciso, 
neste caso, é recuperar instâncias de classes correlatas. 
Uma classe que implemente os métodos getMoeda e getData podem identificar a localidade, através 
de um teste, por exemplo, e retornar a instância da classe identificada. Contudo, esta abordagem é 
inapropriada,   principalmente   à  medida   que   o   número   de   opções  crescer.  Dois   países  não   oferece 
tantos desafios, mas não podemos dizer o mesmo para centenas deles. A manutenção seria dificultada, 
mesmo todo este código estando devidamente confinado em uma única classe. 

Uma   abordagem   mais   atrativa   envolve   o   emprego   de   uma   classe   abstrata, 


LocalidadeAbstractFactory, nossa fábrica abstrata de objetos. Esta classe deve definir os métodos 
getMoeda e getData, mas não implementá­los, tarefa que seria delegada para as subclasses concretas, 
tantas quantas forem as opções existentes. Para o nosso exemplo e para a localidade do Brasil pode­se 
criar a classe  LocalidadeBrasil, para a localidade EUA pode­se criar a classe  LocalidadeEUA, 
conforme ilustra a figura abaixo. 

A implementação dos métodos getMoeda e getData é trivial, simplesmente retornando uma instância 
da classe MoedaBrasil e DataBrasil, respectivamente. Naturalmente, MoedaBrasil herda da classe 
Moeda e DataBrasil herda da classe Data. Estas classes de sufixo Brasil, convém relembrar, devem 
implementar o formato empregado no Brasil para a exibição de valores monetários e de datas. Algo 
similar seria obtido com outra localidade qualquer, conforme ilustra a figura.

9 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

A   solução   apresentada   no   diagrama   acima   faz   uso   do   padrão   Abstract   Factory.   Para   a  aplicação 
cliente, convém ressaltar, há dependências para as classes  LocalidadeAbstractFactory,  Moeda  e 
Data. O cliente depende de LocalidadeAbstractFactory porque é a partir desta que as instâncias 
adequadas de Moeda e Data são obtidas. Naturalmente, código cliente depende das classes Moeda e 
Data, pois são estas que são efetivamente fornecem a funcionalidade desejada. Obserque que o código 
cliente,  ao  fazer uso  de  uma instância de  Moeda  não sabe  se se  trata de uma instância da  classe 
MoedaBrasil  ou  MoedaEUA,   que   foi   retornada   por   uma   classe   derivada   de 
LocalidadeAbstractFactory, também desconhecida da aplicação. 

Exercícios
Implemente o modelo comentado acima, onde as seguintes restrições deverão ser satisfeitas.
1. Uma classe Cliente deverá representar código cliente. 
2. Crie a classe Factory a partir da qual o método newLocalidade() deverá retornar uma instância 
de  LocalidadeAbstractFactory.  A   decisão   deverá   vir   da   configuração   de   um   arquivo   de 
propriedades, conhecido pela classe Factory. A propriedade localidade deverá indicar Brasil para 
a localidade do Brasil ou EUA para a localidade dos EUA. 
3. A classe Cliente, ao fazer uso de Data e Moeda, não deverá conhecer a implementação da classe 
LocalidadeAbstractFactory  assim   como   também   não   conhecerá   quem   implementa   Data  e 
Moeda. 

4. A   funcionalidade   depositada   em  Data  é   apresentar,   conforme   a   localidade,   a   dia   da   semana 


corrente. Por exemplo, para o Brasil pode ser “Seg”, enquanto para a localidade EUA seria “Mon”.
5. A funcionalidade de Moeda é apresentar um valor qualquer na moeda em questão. Por exemplo, 
para o Brasil, R$10,0 é uma saída correta, enquanto para os EUA um valor correto seria $5.00. 
Esta funcionalidade deverá ser obtida, tanto da classe Moeda quanto da classe Data através do 
método toString(), herdado de Object.
6. É bem provável que faça uso da classe java.util.Locale. Para os EUA, já existe um Locale
previamente definido e obtido através de Locale.US. Brasileiros não têm a mesma sorte e terão 
que   criar   uma   instância   desta   classe,   por   exemplo,  Locale brasil = new Locale(“pt”,
“BR”).

7. Imprimir um valor numérico conforme o  Locale  pode ser feito via  java.text.NumberFormat. 


Para os EUA é suficiente NumberFormat.getCurrencyInstance(Locale.US).format(10.0). 
Isto retorna a seqüência de caracteres desejada, ou seja, $10.00.

10 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

8. Para formatar o dia da semana de uma data para determinado Locale pode ser empregada a classe 
java.text.SimpleDateFormat.   Exemplo:  SimpleDateFormat(“E”, Locale.US).   Cria   uma 
instância de  DateFormat  para a qual a mensagem  format(new Date()) oferece o resultado 
esperado. Consulte a documentação da biblioteca Java para detalhes dos argumentos.

Builder (criação)
Em alguns cenários é desejável a criação de objetos distintos, mas cujos processos de criação são 
similares. Objetos que são construídos em vários passos beneficiam­se deste padrão. Demais padrões 
de   construção   enfatizam   a   criação   de   um   objeto   em   um   único   passo.   Este   padrão   envolve   dois 
elementos fundamentais: o Builder e o Director. 
O   Builder   define   as   operações   para   a   criação   de   partes   do   objeto   que   se   deseja   construir.   São 
implementações da interface ou classe abstrata Builder os únicos que sabem como combinar partes do 
objeto que se deseja construir. 
Cada implementação sabe como criar um tipo de objeto específico, mas não possui as partes, que são 
fornecidas pelo Director. O Director, sem conhecer os detalhes da como combinar as partes, mas 
tendo acesso a elas, as repassa para uma implementação do Builder.
O objetivo deste padrão, segundo a GoF, é separar a construção de objetos complexos das respectivas 
representações, de tal forma que o mesmo processo de construção possa criar diferentes objetos. 
Em resumo, o Director sabe como construir um objeto, ou melhor, uma família de objetos cujos 
processos de construção são semelhantes, além de possuir acesso às partes dos objetos que devem ser 
construídos. É de um Builder a responsabilidade por reunir estas partes. Cada Builder dá origem a 
uma representação distinta (objeto distinto). 
A GoF fornece um exemplo onde o Director é um leitor de arquivo RTF e, sem conhecer como um 
arquivo RTF pode ser convertido em uma versão TXT correspondente, cada parte do arquivo RTF 
lido é fornecido a um Builder que implementa a representação TXT. Ao final da leitura do arquivo 
RTF o Director terá requisitado ao Builder a construção do objeto TXT correspondente ao arquivo 
RTF lido. Observe que o emprego do padrão Builder permite, neste exemplo, o acréscimo de novas 
representações de um arquivo RTF, ou seja, novos formatos para os quais um arquivo RTF pode ser 
convertido sem que o leitor de RTF, o Director neste exemplo, tenha conhecimento ou precise ser 
modificado para viabilizar esta conversão. 
Em   outro   cenário,   uma   aplicação   pode   empregar   várias   representações   de   um   volante   da   loteria 
esportiva. Uma delas, por exemplo, pode ser adequada para cálculos estatísticos, enquanto outra mais 
adequada   para   a   simples   impressão   do   volante.   Naturalmente,   qualquer   que   seja   a   representação 
empregada, o processo de criação de um volante é o mesmo e, portanto, o padrão Builer pode ser 
utilizado   para   a   construções   destes   objetos,   onde   teríamos   um   Builder   para   cada   “formato”   final 
desejado e o Director para “ler” os volantes.
O livro Thinking in Patterns, Bruce Eckel, disponível na web, oferece um exemplo que é apresentado 
abaixo com pequenas modificações. Neste exemplo tem­se o interesse na confecção de três objetos 
distintos, Livro, CDROM e Revista, mas cujos processos de criação são similares e, por conseguinte, 
abstratamente capturados pela classe abstrata  MidiaBuilder. Para cada um destes objetos há um 
Builder correspondente, conforme ilustra a figura.
Todos os objetos são derivados da classe Midia. Ao receber um Builder sobre o qual o Director fará 
requisições,   este   saberá   que   o   resultado   final   será   uma   instância   derivada   da   classe   Midia 
correspondente ao Builder recebido. Convém ressaltar que o Director, neste exemplo  MidiaDirector
conforme abaixo, atuará sobre um MidiaBuilder  e, a rigor, não precisa conhecer a classe concreta 
que implementa o Builder desejado.

11 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Exercícios
Faça código que implementa o padrão Builder para tratar o cenário onde vários tipos distintos de 
relatórios   devem   ser   gerados   por   uma   empresa.   Detalhes   seguem   nos   parágrafos   seguintes   e 
empregam a nomenclatura introduzida nesta seção. Tenha a liberdade para implementar, da forma que 
considerar mais apropriada, o que não estiver prescrito abaixo.
O Director, implementado pela classe  RelatorioDirector, é responsável por percorrer uma lista 
onde cada elemento da lista é um array contendo exatamente dois valores do tipo int. Cada array é 
transferido   para   um   Builder   através   da   chamada   ao   método   processaArray(int[])  da   classe 
abstrata RelatorioBuilder. Ou seja, a função do Director é percorrer a lista e, para cada elemento 
desta, enviar a mensagem processaArray para uma instância de RelatorioBuilder. Observe que o 
Director desconhece o que exatamente cada Builder faz em reação à mensagem enviada.
Neste exercício três Builders deverão ser implementados. Um para um relatório simples, um para um 
relatório denominado soma e o terceiro para o relatório denominado de acumulado. Cada um destes 
relatórios são detalhados abaixo. Cada tipo de relatório possui a sua própria classe e o seu próprio 
Builder. Cada um destes Builders, em reação à mensagem  processaArray irá realizar uma operação 
compatível com o tipo de relatório correspondente. 
Cada classe que representa um tipo de relatório herda da classe  Relatorio. Para este exercício, o 
único método relevante da classe Relatorio é o método toString(). 
O relatório simples será criado pelo Builder SimplesBuilder. Uma instância desta classe, à medida 
que recebe mensagens processaArray, simplesmente monta uma lista análoga àquela da instância de 
RelatorioDirector.  Ou seja, cada array será copiado e a cópia acumulada em uma outra lista, a 
lista   que   é   propriedade   da   classe  RelatorioSimples.  SimplesBuilder  cria   uma   instância   de 
RelatorioSimples, que é uma classe derivada de Relatorio. Ao receber a mensagem toString(), 
uma  instância de  RelatorioSimples  exibe  o conteúdo  da lista que retém. Cada entrada da lista 
deverá ser impressa em uma única linha. Os dois valores do tipo  int  de cada entrada deverão ser 
impressos   separados   por   uma   vírgula   e   um   espaço,   conforme   exemplificado   abaixo,   para   uma 
hipotécia lista contendo apenas duas entradas. A primeira com o  array  correspondente contendo os 
valores 1 e 2 e a segunda com o array contendo os valores 3 e 4.
1, 2
3, 4
O relatório soma é implementado pela classe RelatorioSoma. Uma instância de RelatorioSoma  é 
criado pela classe SomaBuilder. RelatorioSoma, à semelhança da classe SimplesBuilder, também 
contém como atributo uma lista. Cada elemento desta lista é um valor do tipo  int, obtido da soma de 
cada array fornecido ao SomaBuilder.
O   relatório   acumulado,   uma   instância   de  RelatorioAcumulado,   também   é   derivada   da   classe 
Relatorio.   Uma   instância   de  RelatorioAcumulado,   contudo,   tem   apenas   uma   propriedade 
relevante,  total, do tipo  int. Esta propriedade deverá conter a soma de todos os valores passados 
para o AcumuladoBuilder. 

12 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Deverão   ser   gerados,   conforme   detalhes   acima,   pelo   menos   10   classes.   São   três   classes 
correspondentes  aos Builders:  SimplesBuilder,  SomaBuilder  e  AcumuladoBuilder.  Outras três 
classes   correspondentes   aos   tipos   de   relatórios:  RelatorioSimples,  RelatorioSoma  e 
RelatorioAcumulado.   Isto   perfaz   seis   classes.   Uma   classe   ancestral   de   todas   estas   três   últimas, 
Relatorio e uma classe abstrata ancestral de todos os Builders, RelatorioBuilder. Também será 
necessária a classe RelatorioDirector que, para este exercício, possui uma lista contendo arrays, 
cada um deles contendo exatamente dois inteiros e uma classe  RelatorioTest  (teste de unidade 
empregando   o   JUnit).   Esta   última   deverá   conter   exatamente   três   métodos:  testeCasoSimples, 
testCasoSoma  e  testCasoAcumulado.   Cada   um   destes   métodos   deverá   possuir   um  assert
apropriado para validar o retorno da mensagem toString() quando enviada para uma instância de 
tipo correspondente. 

Prototype (criação)
A criação de instâncias de algumas classes pode exigir considerável consumo de recursos. De forma 
mais técnica, a execução do construtor pode ser “lenta”, exigir “muita” memória e outros. Nestes 
casos pode ser útil o emprego do padrão Prototype, que visa, em vez de criar uma nova instância de 
uma   classe,   realizar   uma   cópia   de   uma   instância   já   existente.   Desta   forma,   o   ônus   da   criação   é 
substituído pelo ônus da cópia, cuja criação pode ser bem mais eficiente que a criação de um novo 
objeto.
Um   objeto   que   representa   o   resultado   de   uma   consulta   a   todos   os   clientes   de   uma   empresa   que 
compraram   produtos   no   último   mês   pode   retornar   uma   considerável   quantidade   de   informação. 
Imagine que seja necessário  percorrer estes clientes  conforme  a ordem de  compra.  Neste caso, o 
objeto   pode   ser   gerado   nesta   ordem.   Contudo,   se   desejarmos   classificar   os   clientes   em   ordem 
alfabética, ao realizarmos esta operação sobre o objeto resultante, a ordem anterior é destruída. Criar 
um novo objeto para que esta nova classificação possa ser empregada sem destruir a anterior é um 
processo oneroso. Neste caso, a solução é criar uma cópia do objeto obtido com a primeira consulta, o 
que resultaria em dois objetos, com o mesmo conteúdo mas custo de criação bem diferentes. 

Clones em Java
A classe Object em Java contém o método protected Object clone(). Ao receber a mensagem 
close(), o resultado esperado é uma cópia do objeto que a recebe. Cada classe deve implementar a 
sua   própria   concepção   de   cópia.   Também   é   necessário   que   a   classe   implemente   a   interface 
Cloneable. Estas restrições, juntamente com o sentimento de que é muito mais seguro e fácil fazer 
uso de um construtor que recebe como argumento um objeto da classe em questão, torna o emprego 
deste método desaconselhável por alguns.
Adicione às questões apresentadas o fato do método  clone()  realizar o que é conhecido por cópia 
rasa ou parcial (shallow copy).  Ou seja, as referências são copiadas, mas não os dados subjacentes. 
Isto significa que uma mudança em um objeto referenciado por uma cópia irá repercutir igualmente no 
objeto original e vice­versa. Em alguns casos isto pode ser indesejável e uma cópia total (deep copy) 
deve ser empregada. O método abaixo pode ser empregado em uma classe para permitir a cópia total 
de um objeto, ou seja, todas as referências e os dados correspondentes são copiados. O método exige 
que   o   objeto   fornecido   como   argumento   implemente   a   interface  java.io.Serializable.   Esta 
exigência é da classe ObjectOutputStream.

public static Object deepCopy(Object o) {


try {
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
ObjectOutputStream oOut = new ObjectOutputStream(bOut);
oOut.writeObject(o);

ByteArrayInputStream bIn;

13 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

bIn = new ByteArrayInputStream(bOut.toByteArray());


ObjectInputStream oi = new ObjectInputStream(bIn);
return oi.readObject();
} catch (Exception e) {
return null;
}
}

Observe que este método não copia apenas o objeto fornecido mas todo o grafo que pode ser obtido a 
partir deste objeto. Ou seja, se o objeto fornecido como argumento possui uma referência para outro 
objeto e este para outro e assim por diante, todos eles serão copiados. Naturalmente, se se deseja 
copiar apenas o objeto fornecido como argumento e não os objetos que podem ser obtidos a partir 
deste, então será necessário empregar outro método para a cópia.

Exemplo
A classe  Ponto, fornecida abaixo sobrescreve o método  clone()  herdado de  Object. Observe a 
forma simples empregada pela classe para se obter uma nova instância com base em uma existente. 
Ao   contrário   do   método  deepCopy(),   comentado   anteriormente,   a   implementação   de  clone()
fornecida abaixo faz uso do construtor de Ponto.

package prototype;

public class Ponto {


private int x = 0;
private int y = 0;

public Ponto(int x, int y) {


this.x = x;
this.y = y;
}

public Object clone() {


return new Ponto(x,y);
}

public boolean equals(Object outro) {


Ponto ponto = null;
if (! (outro instanceof Ponto))
return false;
else
ponto = (Ponto) outro;
return x == ponto.x && y == ponto.y;
}
}

Exercícios
1. Crie código para exercitar o uso do método clone().
2. Faça uma implementação da abordagem deep copy e realize testes para constatar que a cópia está 
sendo realizada da forma esperada.

Singleton (criação)
Este padrão é empregado para assegurar que apenas uma instância de determinada classe será criada 
por aplicação. Há aplicações que devem fazer uso de uma única instância para acesso a informações 
persistentes,   por  exemplo.   Nestes  casos,   a  existência   de   mais  de  um  objeto   pode   comprometer   a 
integridade das informações. 
O padrão Singleton também pode ser empregado para oferecer um ponto de acesso comum a um 
objeto. Por exemplo, objetos Factory (3), geralmente são  empregados em conjunto  com o  padrão 
Singleton, de tal forma que exista um único objeto Factory para cada finalidade desejada e um único 

14 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

ponto   de   acesso   ao   mesmo.   Entenda   um   único   ponto   de   acesso   como   visibilidade   global,   onde 
qualquer parte da aplicação pode ter acesso ao objeto desejado. 
A   figura   abaixo   ilustra   a   aplicação   deste   padrão   na   classe  Singleton.   Observe   o   emprego   da 
propriedade  instance,   declarada   privada;   do   construtor   declarado  protected  e   do   método 
getInstance(). Embora não seja uma exigência, é comum o emprego do método  getInstance para 
se obter uma instância da classe que recebe esta mensagem. 
Esta é uma das três opções de implementação deste padrão. O método   getInstance()  é chamado 
para   retornar   a   instância   desejada.   Em   conseqüência,   este   método   deve   ser   declarado   público   e 
static, o que assegura o acesso à instância de interesse onde quer que a classe   Singleton  seja 
visível, pois onde esta for visível será possível enviar esta mensagem.

Singleton
­ instance : Singleton

# Singleton()
+ getInstance() : Singleton

Uma implementação induzida pelo modelo acima é fornecido a seguir. Observe que o código tem 
propósito   didático   e,   portanto,   as   operações   “reais”   que   estariam   presentes   foram   abstratamente 
representadas pelo método operation().  

public class Singleton {


private static Singleton instance;

protected Singleton () {
}

public static Singleton getInstance() {


if (instance == null) {
instance = new Singleton();
}
return instance;
}

public void operation() {


System.out.println (“trabalhando...”);
}
}

Opções de implementação
A classe Impressora apresenta outra proposta de implementação do padrão Singleton. Nesta versão, 
uma variável lógica indica se a instância desejada já foi ou não criada. Quando se tenta criar uma 
instância desta classe, o construtor consulta a variável. Se pelo menos uma instância já foi criada e se 
encontra   em   “uso”,   então   uma   exceção   é   gerada.   Caso   contrário,   o   construtor   é   executado 
normalmete, o que oferece a instância desejada para quem tenta criar a instância. 

class Impressora {
static boolean existeInstancia = false;

public Impressora() throws SingletonException {


if (existeInstancia) {
throw new SingletonException("Instancia ja existe");
} else {
existeInstancia = true;
}
System.out.println("Instancia de Printer criada");
}

public static void fim() {


existeInstancia = false;

15 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

}
}

class SingletonException extends RuntimeException {


public SingletonException() {
super();
}

public SingletonException(String s) {
super(s);
}
}

Esta proposta, embora assegure a existência de uma única instância, apresenta uma responsabilidade 
adicional ao programador, que será responsável pela chamada ao método   fim()  para indicar que a 
instância  em  questão   não   mais  será  empregada.   Naturalmente,   esta   exigência  pode   estar  além   da 
disciplina da equipe de programação e, portanto, suscetível a problemas.
Uma terceira versão é fornecida abaixo.

package singleton;

public class Teclado {


static boolean existeInstancia = false;

private Teclado() {
}

static public Teclado getInstance() {


if (!existeInstancia) {
existeInstancia = true;
return new Teclado();
} else {
return null;
}
}

protected void finalize() {


existeInstancia = false;
}
}

Exercícios
1. Crie uma classe, por exemplo, Biblioteca, e assegure que aplicações que farão uso desta classe 
só terão acesso a no máximo uma instância desta classe por processo.
2. Estabeleça comparações entre as versões 1, 2 e 3 para a implementação do padrão Singleton. 
Quais as vantagens de um em relação ao outro. 
3. Escreva uma aplicação que, em instantes distintos da sua execução, faça uso de duas instâncias 
distintas de  Impressora.  Sugestão:  após criar uma instância, não mais retenha uma referência 
para o objeto criado e “tente” forçar a execução do garbage collector (por exemplo, via chamada 
a System.gc()).
4. Discuta a possibilidade ou não de fazer o mesmo do item anterior para a classe Singleton.
5. Modifique a classe  Singleton  para que sempre, mesmo que uma instância não seja utilizada, 
esta se encontre à disposição. 
6. O trecho de código abaixo foi extraído de um programa cujo autor diz que mostra uma falha na 
implementação  da classe  Singleton, fornecida neste texto, pois permite o emprego  de duas 
instâncias   simultâneas   onde   só   poderia   existir   uma.   Verifique   a   afirmação   deste  hacker.  
public static void main(String[] args) {

16 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Singleton s = getInstance();
QuebraSingleton qs = new QuebraSingleton();

System.out.println(qs instanceof Singleton);


System.out.println(s == qs);
}

7. Crie   uma   variante   do   padrão   Singleton   que   permite   a   criação   de   um   número   específico   de 
instâncias em vez de apenas uma. Este padrão também é conhecido por Object Pool.
8. A biblioteca Java possui uma implementação do padrão Singleton não comentada anteriormente. 
Uma classe declarada final cujos métodos são todos public e o construtor é private também 
é uma forma de implementar o padrão Singleton, embora aplicável em casos específicos. Qual é 
a classe que atende a tais características?
9. O que ocorre quando se tenta criar uma instância de uma subclasse de um Singleton que já possui 
uma instância?
10. As   opções   de   implementação   apresentadas   acima   não   contemplam   a   execução   concorrente 
(multithread). O que é necessário modificar para viabilizar as opções acima em um programa 
concorrente?

Adapter (estrutura)
Nas relações internacionais do Brasil com a China é necessária a existência de intérpretes, pelo menos 
em alguns casos. Um interlocutor, nestes casos, é responsável pela comunicação 
entre   partes   que   fazem   uso   de   línguas   bem   distintas.  Apesar   das   diferenças,   a 
comunicação deve ser estabelecida.  O amigo chinês não se fará entender sem o 
intérprete, assim como o brasileiro. De forma análoga, um aparelho que opera em 
110V exige um transformador para uma tomada 220V. Todos eles são “adaptadores”, assim  como o 
objeto ao lado.
Imagine  o  cenário   em  que uma aplicação   precisa  ser freqüentemente  alterada  para contemplar as 
novas versões de uma classe amplamente empregada, onde cada nova versão apresenta diferenças em 
relação a anterior. Por exemplo, suponha que em uma das mudanças a classe OfereceServico possui 
o   método  fazAlgumaCoisa(),   que   foi   substituído   por  fazOutraCoisa() em   versão   posterior. 
Também suponha que foi removido o argumento do método   faz(String), o que resulta na versão 
faz(),   onde   o   argumento   deverá   ser   fornecido   através   da   chamada   a   um   novo   método, 
setString(String), antes que  faz()  seja chamado. Esta mudança é ilustrada na figura abaixo, 
contendo a versão resultante e aquela anterior.

OfereceServico OfereceServico
Versão antes Versão após
de mudança. + fazOutraCoisa() mudança.
+ fazAlgumaCoisa()
+ setString(String)
+ faz(String)
+ faz()

Este cenário sugere mudanças na aplicação para que a nova versão possa ser utilizada sem problemas. 
Ou seja, quando OfereceServico se altera, código cliente desta classe também terá que ser alterado, 
conforme ilustra a figura abaixo. Noutras palavras, há dependência entre a aplicação (cliente) e a 
classe  OfereceServico. Não há nada inerentemente errado com a dependência entre duas classes, 
exceto   quando   a   classe   dependida   é   instável   e   nenhum   mecanismo   de   proteção,   como   o   padrão 
Adapter, é empregado.

17 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Sempre que esta classe for 
Abstração de código da  alterada, Cliente deverá ser 
'aplicação' que faz uso  investigado à procura de 
de OfereceServico. mudanças correspondentes.

Cliente OfereceServico

Felizmente o padrão Adapter pode ser empregado para proteger a aplicação das variações da classe 
OfereceServico.   Na presença  de um  adaptador, mudanças na  classe  OfereceServico  poderão 
repercutir, no máximo, no adaptador, o que protege qualquer cliente de possíveis alterações. Como 
isto pode ser feito?
Os serviços requisitados por um cliente podem ser encapsulados em uma interface. Desta forma, o 
cliente não depende de uma implementação específica, mas apenas da interface, conforme é ilustrada 
o diagrama abaixo. Neste caso, qualquer que seja a implementação, desde que a interface não seja 
alterada, a classe Cliente não será afetada.

Depende das operações definidas na  Implementa operações 
Interface. Desconhece a existência de  da Interface.
OfereceServico.

Cliente OfereceServico
Interface

Neste diagrama a classe  OfereceServico  pode ser alterada sem que isto provoque mudanças na 


classe  Cliente,   que   depende   de  Interface  em   vez   da   implementação   desta   interface 
OfereceServico. Não há dependência entre as classes Cliente e OfereceServico. 

Enquanto para alguns casos o simples uso de uma interface pode ser suficiente, há aqueles onde uma 
abordagem   mais   elaborada   faz­se   necessária.   Afinal,   o   emprego   de   uma   interface   restringe 
repercussões na classe Cliente e transfere todo o ônus de alterações para a classe que implementa a 
interface. 
No   cenário   de   mudança   comentado   acima,   por   exemplo,   ao   alterar   o   nome   do   método 
fazAlgumaCoisa()  por  fazOutraCoisa(),   teríamos   que   manter   ambos   os   métodos   na   classe 
OfereceServico, pois a classe  Cliente  fará uso do método  fazAlgumaCoisa(). Assuma que os 
serviços desejados são aqueles presentes na versão antiga, ou seja, aquela antes da alteração. Em 
conseqüência,  OfereceServico  terá   que   mantér   este   método   e   acrescentar  fazAlgumaCoisa()
conforme o código abaixo.

public void fazAlgumaCoisa() {


fazOutraCoisa();
}

Observe que, desta forma, não teríamos de fato uma “nova” versão da classe  OfereceServico, mas 
uma   classe   que   mantém   a   compatibilidade   com   a   versão   anterior   e,   ao   mesmo   tempo,   acomoda 
mudanças. Contudo, esta solução só é possível quando se tem controle sobre a nova versão da classe. 
Nem sempre isto ocorre. O fornecedor desta classe pode não estar disposto a manter a compatibilidade 
com versões anteriores e simplesmente renomear o método. O modelo abaixo ilustra o emprego do 
padrão Adapter para sanar este problema.

18 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

public class Adaptador implements Interface {
      OfereceServico adaptado;
A classe Adaptador encapsula
...
mudanças em OfereceServico, 
      public void fazAlgumaCoisa() {
tornando­as invisíveis às classes que 
            fazOutraCoisa();
fazem uso de Interface.
      }
...
}

<<Interface>>
Cliente Interface Adaptador
+ fazAlgumaCoisa()

adaptado Em versão anterior possuía 
fazAlgumaCoisa(), cujo 
Em algum método desta classe,  OfereceServico nome foi alterado para 
onde obj é referência para instância  fazOutraCoisa() sem 
de Adaptador, vê­se ... repercussão em Cliente.

obj.fazAlgumaCoisa();

Neste modelo  o  padrão   Adapter  é  explorado  em sua totalidade.  Observe  que  OfereceServico  e 


Cliente  são   classes   independentes,   desconhecidas   uma   da   outra.   Esta   independência   também   se 
verifica entre  Cliente  e  Adaptador. Alterações na classe  OfereceServico  são encapsuladas pela 
classe Adaptador, que implementa Interface. 
Observe a classe Cliente, fornecida abaixo, nenhuma dependência existe para as classes Adaptador
e OfereceServico. A classe Cliente depende somente da interface Interface. Naturalmente, em 
tempo de execução, uma instância de uma classe que implementa  Interface  será empregada, mas 
observe   que   a   instância   de  Cliente  que   estará   fazendo   a   requisição   não   saberá   quem   estará 
oferecendo o serviço. 

public class Cliente {


Interface obj = null;

public Cliente(Interface int) {


obj = int;
}

public void umaTarefa() {


obj.fazAlgumaCoisa(); // Requisita serviço, desconhece quem o executa.
}
}

Observe   que   a   mudança   do   nome   da   operação  fazAlgumaCoisa()  para  fazOutraCoisa(),   por 


exemplo,   provoca   alteração   no   código   da   classe  Cliente  pois,   neste   caso,   houve   mudança   em 
Interface e a classe Cliente depende de Interface. Contudo, se não houver mudança na interface 
Interface, não é necessária nenhuma alteração na classe Cliente, mesmo que a classe responsável 
pela execução desta operação seja alterada, ou seja, tenha o método fazAlgumaCoisa() substituído 
por outro de nome fazOutraCoisa(). Vejamos como isto é possível.
Abaixo segue uma versão da classe OfereceServico que implementa o método fazAlgumaCoisa().

public class OfereceServico {


public void fazAlgumaCoisa() {
System.out.println(“Algo realmente simples”);
}
}

A   versão   seguinte,   ao   contrário   da   anterior,   substitui   o   identificador   fazAlgumaCoisa  por 


fazOutraCoisa. Esta simples mudança, sem o emprego de um adaptador, forçaria uma alteração 
correspondente   na   classe  Cliente.   Felizmente   estamos   empregando   o   padrão  Adapter  e,   em 
conseqüência, nenhuma alteração é necessária em Cliente. 

19 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

public class OfereceServico {


public void fazOutraCoisa() {
System.out.println(“Algo realmente simples”);
}
}

A classe  Adaptador  é fornecida abaixo. A associação  direcionada da classe  Adaptador  para a 


classe  OfereceServico  é   implementada   através   da   variável   de   instância  adaptado,   conforme   o 
modelo  anteriormente apresentado   sugere.  Sugere deve ser  aqui interpretado  no  sentido  literal do 
termo, pois enquanto modelo, o diagrama anterior apenas “orienta” uma possível implementação, em 
vez de prescrever.  

public class Adaptador implements Interface {


OfereceServico adaptado = null;

public Adaptador() {
adaptado = new OfereceServico();
}

public void fazAlgumaCoisa() {


adaptado.fazOutraCoisa();
}
}

Observe   como   foi   implementado   o   método  fazAlgumaCoisa().   Em   decorrência   da   mudança   na 


classe  OfereceServico, responsável pela implementação dos serviços (lembre­se de que a classe 
Adaptador  é   apenas   um   intermediário),   não   mais   existe   método   com   este   nome   em 
OfereceServico. Em conseqüência, a classe Adaptador  faz a adaptação necessária ao converter a 
mensagem recebida naquela que de fato irá prestar o serviço requisitado, encapsulando a mudança e 
mantendo a independência entre Cliente e OfereceServico.
Neste   ponto   deveria   ser   clara   a   alteração   correspondente   envolvendo   o   método  faz(String), 
substituído por  faz()  e  setString(String). Para que não fiquem dúvidas, observe a versão da 
classe OfereceServico abaixo.

public class OfereceServico {


public void faz(String str) {
System.out.println(str); // Faz algo simples com str
}
}

Também observe a versão resultante das mudanças naquela anterior.

public class OfereceServico {


String local = null;

public void setString(String str) {


local = str;
}

public void faz(String str) {


System.out.println(str); // Faz algo simples com str
}
}

Sem o emprego do padrão Adapter, teríamos que fazer mudanças correspondentes na classe Cliente
conforme   ilustrado   abaixo,   pois   a   interface   empregada   seria   alterada.   Observe   que   a   alteração   é 
simples devido ao emprego trivial que é feito. Em casos reais provavelmente mais de uma classe 
estaria fazendo uso da interface, assim como este uso estaria distribuído, provavelmente, por vários 
métodos.  

20 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Sem o emprego do padrão Adapter

Versão antes da alteração na interface Versão após alteração na interface
public class Cliente { public class Cliente {
Interface obj = null; Interface obj = null;

public Cliente(Interface int) { public Cliente(Interface int) {


obj = int; obj = int;
} }

public void umaTarefa() { public void umaTarefa() {


obj.faz(“umaTarefa”); obj.setString(“umaTarefa”);
} obj.faz();
} }
}

Com o emprego do padrão Adapter, a versão à esquerda na tabela acima não precisa sofrer alterações 
quanto a classe  OfereceServico  é alterada. Conforme comentado anteriormente, as mudanças são 
encapsuladas em Adaptador, que protege a classe Cliente de variações na classe OfereceServico. 
A tabela abaixo mostra a alteração na classe  Adaptador  necessária para que a classe  Cliente  seja 
mantida intacta, enquanto  a classe  OfereceServico  sofre alterações. Observe  ainda  que a  classe 
Adaptador  é  apenas   um  intermediário,  em   ambos  os   casos.   Quem  executa   os  serviços  nas   duas 
versões é a classe OfereceServico.

Emprego do padrão Adapter

Adaptador antes da alteração em OfereceServico Adaptador após da alteração em OfereceServico
public class Adaptador implements Interface { public class Adaptador implements Interface {
OfereceServico adaptado = null; OfereceServico adaptado = null;

public Adaptador() { public Adaptador() {


adaptado = new OfereceServico(); adaptado = new OfereceServico();
} }

public void faz(String str) { public void faz(String str) {


adaptado.faz(str); adaptado.setString(str);
} adaptado.faz();
} }
}

Finalmente podemos  apresentar  um pequeno programa  que  faz uso da classe   Cliente  e  de duas 


versões   da   classe  OfereceServico  e   das   correspondentes   versões   da   classe  Adaptador.   Este 
exemplo também ilustra como versões distintas podem coexistir, talvez fornecidas por fabricantes 
distintos. Neste cenário estaríamos interessados no desenvolvimento de uma aplicação que faz uso de 
serviços oferecidos por terceiros. Dada a variedade de fornecedores e as correspondentes implicações 
como preço,  desempenho,  suporte  e  outros atributos,  o  padrão  Adapter pode ser  empregado  para 
proteger nossa aplicação das variantes existentes no mercado. 
Apenas para reforçar, abaixo segue o código da classe Cliente, aqui representando todo e qualquer 
código da nossa aplicação que faz uso dos serviços oferecidos por terceiros. Esta classe é única, não 
possui duas versões. De fato, esta classe sequer sabe que há alterações no mundo ao redor.

public class Cliente {


Interface obj = null;

public Cliente(Interface int) {


obj = int;
}

21 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

public void umaTarefa() {


obj.faz(“umaTarefa”);
}
}

As   duas   versões   da   classe  OfereceServico  serão   identificadas   por  OfereceServicoV1  e 


OfereceServicoV2.   De   forma   similar,   também   teremos  AdaptadorV1  e  AdaptadorV2, 
respectivamente os adaptadores para as classes OfereceServicoV1 e OfereceServicoV2. 

Versão V1 Versão V2

public class AdaptadorV1 implements Interface


{
OfereceServico adaptado = null;
public class OfereceServicoV1 {
public Adaptador() {
public void faz(String str) {
adaptado = new OfereceServicoV1();
System.out.print(“V1: ”);
}
System.out.println(str);
}
public void faz(String str) {
}
adaptado.faz(str);
}
}

public class AdaptadorV2 implements Interface


public class OfereceServicoV2 {
{
String local = null;
OfereceServico adaptado = null;
public void setString(String str) {
public Adaptador() {
local = str;
adaptado = new OfereceServicoV2();
}
}
public void faz() {
public void faz(String str) {
System.out.print(“V2: ”);
adaptado.setString(str);
System.out.println(local);
adaptado.faz();
}
}
}
}

Para representar a nossa aplicação é apresentada a classe abaixo, responsável por criar duas instâncias 
da classe  Cliente, cada uma delas fará uso de uma implementação distinta da interface utilizada. 
Para   que   não   fique   dúvidas   acerca   das   versões   distintas,   observe   que   as   implementações   de 
OfereceServicoV1  e  OfereceServicoV2  acrescentam   um   prefixo   antes   de   exibir   a   String 
requisitada. 

Cliente Public class AdapterTeste {


public static void main(String[] args) {
Cliente c1 = new Cliente(new AdaptadorV1());
Cliente c2 = new Cliente(new AdaptadorV2());

c1.faz(“um teste”);
c2.faz(“um teste”);
}
}

A execução do código acima faz uso de uma única classe Cliente cuja instância referenciada por 
c1  comporta­se  diferente  daquela  referenciada  por  c2.   De  fato,   podemos  ter  comportamentos  tão 
distintos quanto desejarmos sem modificações correspondentes na classe  Cliente, desde que seja 
mantida a interface. Isto permite, conforme já foi afirmado anteriormente, a independência entre a 
classe Cliente e a implementação dos serviços por esta utilizados. 

22 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

MOTIVAÇÃO. Convém ressaltar que este é apenas um exemplo didático onde a alteração efetuada é 
mínima,   assim   como   a   repercussão   desta.   Em   casos   reais,   contudo,   podemos   ter   dezenas,   talvez 
centenas ou mais classes dependentes que podem exigir alterações correspondentes. Alguns podem 
dizer   que   toda   a   mudança   pode   ser   efetuada   com   uma   “substituição   global   de   texto   por   outro”. 
Francamente falando, não conheço ninguém que não se sinta desconfortável com esta tarefa perante 
um   sistema   de   centenas   de   milhares   de   linhas   de   código.   Será   necessário   recompilar   todas   as 
mudanças e realizar testes. Se isto  não é motivação suficiente, talvez seja o fato de que a classe 
Cliente  e a classe  OfereceServico  possam ser fornecidas por empresas distintas, onde uma não 
tem acesso ao código da outra e vice­versa. Neste cenário, uma “substituição global de texto por 
outro”, por menor e mais simples que seja, não é viável. 
Ou   seja,   quando   uma   instância   de  Cliente  requisita   a   execução   de   uma   operação   definida   na 
Interface,  será  da  responsabilidade  da  instância  de Adaptador  a  execução   requisitada.   A  classe 
Adaptador,   por   sua   vez,   como   o   próprio   nome   indica,   funciona   apenas   como   intermediário   ao 
traduzir   as   requisições   da   instância   de  Cliente  em   chamadas   a   instância   empregada   de 
OfereceServico. O diagrama de seqüência abaixo ilustra uma interação típica entre instâncias destas 
classes. 

Enviada por instância de Cliente sem conhecimento da 
instância de Interface que irá recebê­la.

 : Cliente  : Adaptador  : OfereceServico

1: fazAlgumaCoisa( )

2: fazOutraCoisa()

Exercícios
1. O método substring(int beginIndex, int endIndex) da classe java.lang.String tem 
um comportamento peculiar quanto aos argumentos. O argumento beginIndex indica a posição 
de início na  String  em questão a partir da qual uma subseqüência será obtida. O fim não é 
fornecido por endIndex, mas por endIndex – 1. Ou seja, se você deseja obter a seqüência que 
se inicia no primero caractere e termina no segundo, terá que fornecer como argumentos  0 e 2. 
Naturalmente isto pode confundir muita programador. Supondo que a classe java.lang.String
é   extensivamente   utilizada   por   um   sistema   e   que   é   muito   freqüente   o   erro   associado   aos 
argumentos de substring, o que poderia ser feito para amenizar o problema? (Dica: crie a sua 
classe  String  que   irá   funcionar   como   um   adaptador.   Esta   classe   deverá   conter   os   métodos 
empregados   da   classe  java.lang.String.  Provavelmente   terá   que   fazer   uma   substituição 
global   onde   java.lang.String  seja   substituída   por  br.ufg.inf.meuprojeto.String,   por 
exemplo. A implementação da sua classe fará uso de java.lang.String. Quando a mensagem 
substring  for   recebida   está   será   transmitida   para   a   instância   de   java.lang.String
correspondente com uma pequena alteração no argumento da seguinte forma:  substring(x,y)
resultará na chamada a substring(x,y+1).)

Bridge (estrutura)
Desenvolver software orientado a objeto envolve, freqüentemente, o emprego de herança, onde uma 
subclasse   encapsula   um   caso   particular   como   a   implementação   de   um   serviço   para   determinado 
cenário. Por exemplo, a classe  ImpressoraX  herda da classe Impressora alguns serviços enquanto 
outros são sobrescritos como imprime(String), que exige uma implementação distinta para a classe 

23 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

ImpressoraY.   Provavelmente,   para   cada   fabricante   de   impressora   teremos   uma     implementação 


correspondente em uma subclasse de Impressora.
Conforme  a   GoF   ressalta,   nem  sempre  esta   abordagem  é  suficiente.   Observe  que  teremos   uma 
classe distinta para cada contexto e, por exemplo, se um serviço é acrescido à classe  Impressora,
então   toda   uma   hierarquia   terá   que   ser   revista.   Nesta   abordagem,   o   serviço   e   a   respectiva 
implementação residem em uma mesma hierarquia. O padrão Bridge oferece uma alternativa onde a 
abstração   (serviço)   é   colocada   em   uma   hierarquia   distinta   daquela   que   implementa   a   abstração. 
Vejamos um exemplo.
Seja Documento uma classe que abstratamente representa documentos do mundo real manipulados 
por um software. A classe Requisição herda da classe Documento assim como Comprovante, entre 
muitas   outras.   Um   método   abstrato   (serviço)   fornecido   na   classe  Documento  e   de   interesse   dos 
clientes   é  formatar().   Esta   mensagem,   quando   enviada   à   uma   instância   de  Documento  deverá 
retornar uma String cujo conteúdo está pronto para ser exibido. Para os clientes, conforme ilustra a 
figura abaixo, a dependência está restrita à classe abstrata Documento.

Se desejarmos uma implementação do serviço adequada para a exibição em monitores pequenos 
como   aqueles   de   dispositivos   móveis,   provavelmente   terminaremos   com   duas   subclasses   para 
Requisição e outras duas para Comprovante. Cada uma delas tratando especificamente cada um dos 
cenários apresentados, conforme o diagrama abaixo ilustra. Foram empregados os sufixos  Padrão e 
Menor, respectivamente, para representar o monitor convencional e aquele de dimensões reduzidas.

Documento

+ formatar()

Requisição Comprovante

ReqPadrão ReqMenor CompPadrão CompMenor

Embora o modelo acima implemente os requisitos apresentados, observa­se facilmente que para 
cada tipo de documento teremos duas subclasses. O cenário é ainda menos favorável se imaginarmos 
o surgimento de um novo tipo de monitor, pois teríamos um crescimento significativo do número de 
classes e, com certeza, uma dificuldade na manutenção delas. Felizmente, os inconvenientes deste 
cenário podem ser eliminados com o emprego do padrão Bridge. 
O que chama a atenção nesta hierarquia é que residem nela tanto a abstração de documentos quanto 
as   implementações  correspondentes.   O   padrão   Bridge  sugere   que  a   abstração,   ou   seja,   as   classes 
Documento,  Requsição  e  Comprovante  sejam   mantidas   em   uma   hierarquia   distinta   daquela   da 
implementação, onde residem as classes Formatação, Padrão e Menor, conforme ilustra o diagrama 
abaixo. 

24 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

formatar(str) { Documento 1 Formatação


    estilo.formatar(str);
}
+ formatar() estilo + formatar()

Requisição Comprovante Padrão Menor

+ toString() + formatar() + formatar()

toString() {
  return formatar(str);
}

Observe   que   a   hierarquia   anterior   foi   fragmentada   em   duas:   uma   contendo   as   abstrações   de 
documentos, relevante para a classe  Cliente, e outra contendo a implementação destas abstrações. 
Neste exemplo, a implementação resume­se ao serviço de formatar documentos para exibição em dois 
tipos de monitores. No futuro, caso novas formas de formatação sejam criadas, a inserção não irá 
interferir nos clientes da classe  Documento, assim como também não provocarão um crescimento 
significativo do número de classes e a decorrente dificuldade de manutenção.
Convém ressaltar que a classe  Documento  e suas subclasses não terão que se preocupar com a 
formatação para dispositivos específicos, pois este conhecimento foi isolado na hierarquia da direita. 
O método toString(), conforme a nota, deverá chamar o método formatar() sem conhecer quem 
estará oferecendo este serviço. No  processo de construção de uma instância de uma subclasse de 
Documento, o construtor pode receber como argumento a formataçao a ser empregada, isto é, uma 
instância   de  Formatação,   seja   esta   da   classe  Padrão  ou  Menor.   O   padrão   Factory   (3)   pode   ser 
empregado para esta finalidade. 
Outro exemplo. Pode­se interpretar estruturas de dados como listas, filas e árvores como abstrações 
em uma hierarquia. Cada uma destas com sua respectiva classe e serviços, alguns herdados da classe 
ancestral   abstrata  EstruturaDeDados.   Seja  ordena()  um   dos   serviços   definidos   nesta   classe. 
Sabemos que vários são os métodos que podemos empregar para ordenação e, neste caso, o que se 
deseja evitar, através do emprego do padrão Bridge, é a criação de classes como  ListaBubleSort, 
ListaQuickSort e assim por diante, cada uma delas, naturalmente, implementando  ordena() com o 
método correspondente ao sufixo empregado. O padrão Bridge sugere que criemos uma hierarquia 
cuja classe no topo é Ordenação e subclasses  QuickSort  e  BubleSort, por exemplo. Desta forma, 
um   novo   método   de   ordenação   que   se   fizer   interessante   para   determinado   contexto   pode   ser 
empregado sem que tenhamos que criar tantas classes quanto o número de estruturas de dados criadas. 
Não é difícil encontrar possibilidades de implementação do padrão Bridge, assim denominado por 
estabelecer uma “ponte” entre as hierarquias comentadas anteriormente (aquela da abstração e aquela 
da implementação). 

Exercícios
Implemente código que satisfaz os seguintes itens.
1. Em casos mais simples pode ser suficiente a implementação da persistência de classes de negócio 
ser fornecida na própria classe. Assim, a classe  Pessoa, que herda de  ClasseNegocio, assim 
como a classe Carro, implementam o método abstrato persiste(), herdado de ClasseNegocio. 
2. Faça  uso   do   padrão   Bridge  para  implementar  a  hierarquia  de  classes  de  negócio   “isolada”  da 
implementação de persistência, que pode empregar um de dois métodos: XML e serialização. O 
emprego  do  padrão  Bridge,  convém ressaltar,  facilita o  acréscimo  de outros métodos,  como  o 
emprego de um SGBD, por exemplo. 
3. Não é necessário implementar efetivamente os métodos acima. Simule a persistência com uma 
simples mensagem exibida na saída padrão indicando o método empregado seguido do conteúdo 
do objeto a ser armazenado. Este conteúdo pode ser obtido simplesmente através do retorno do 
método toString().

25 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Composite (estrutura)
Sistemas   de   informação   manipulam   coleções   de   objetos,   muitas   vezes   compostas   por   elementos 
simples e outras coleções. Por exemplo, um grupo pode ser formado por pessoas e outros grupos. Em 
alguns casos é desejado um tratamento para o grupo semelhante ao de um elemento. Nestes cenários, 
o padrão Composite oferece uma solução. 
Um   exemplo   do   padrão   Composite   é   freqüentemente   encontrado   em   editores   gráficos.   Nestes, 
vários elementos gráficos como linha, retângulo, texto e outros podem ser agrupados, formando um 
novo elemento. Este novo  elemento, por sua vez, juntamente com outras linhas e retângulos, por 
exemplo, podem ser agrupadas, dando origem a um novo agrupamento. 
Chamemos   cada   um   destes   agrupamentos   de   elemento   composto.   Ou   seja,   uma   agregação   de 
ElementoGráfico dá origem a uma instância de ElementoComposto, conforme figura abaixo. 

ElementoGráfico elementos

+ desenhe() 2..*

Texto Círculo Linha ElementoComposto

A classe abstrata ElementoGráfico é uma abstração para todo tipo de elemento gráfico que pode ser 
manipulado   pelo   editor   do   nosso   exemplo.   O   diagrama   ilustra   apenas   alguns   dos   tipos   por 
simplicidade:  Texto,  Círculo,  Linha  e  ElementoComposto.  ElementoComposto  representa  uma 
agregação de elementos gráficos, pelo menos dois deles. Desta forma, quando o usuário “agrupar” 
elementos   devidamente   selecionados,   esta   operação   dará   origem   a   uma   instância   de 
ElementoComposto, cujas partes são os elementos selecionados que pode conter, inclusive, outros 
elementos compostos. 
A única operação fornecida em ElementoGráfico é desenhe(). Todo editor gráfico precisa pintar e 
repintar os elementos que estão sendo editados, inclusive aqueles compostos. Dessa forma, quando 
uma instância de ElementoComposto receber a mensagem desenhe(), será da responsabilidade desta 
instância retransmitir esta mensagem para cada uma de suas partes. Isto ilustra o emprego do padrão 
Composite.
O  padrão  Composite  é  formado   por  quatro   participantes  principais:  um  cliente,  quem  faz uso   da 
aplicação do padrão; classes concretas que são utilizadas pelo cliente, todas elas herdam de uma classe 
abstrata; a classe abstrata que define as operações comuns a todas as partes (as classes concretas); e, 
em   particular,   a   classe   concreta   que   agrega   instâncias   de   classes   derivadas   da   classe   abstrata. 
Tipicamente o cliente requisita uma operação para alguma instância de uma das classes concretas. O 
cliente pode “desconhecer” o objeto que de fato recebe a mensagem. Isto é possível através de uma 
referência para  a classe abstrata. Qualquer que seja  o  objeto, contudo,  seja ele um elemento   não 
composto   ou   composto   por   outros   elementos,   a   operação   será   executada   transparentemente   da 
perspectiva do cliente. 
O padrão Composite também pode ser empregado para o registro de hierarquias. Por exemplo, uma 
classe   abstrata  Membro  herdada   por   duas   outras   classes,  Pessoa  e  Grupo,   onde  Grupo  é   uma 
agregação de Membro, é suficiente para o registro de um organograma. Na raiz teríamos uma empresa 
(instância de  Grupo) cujos membros seriam um presidente (instância de  Pessoa) e departamentos 
(instâncias de  Membro), cada um destes departamentos por sua vez estariam organizados em outros 
departamentos e pessoas até que tenhamos apenas pessoas, encerrando a hierarquia.  

26 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Um outro emprego do padrão Composite é a descrição de estratégias (regras de negócio). Neste caso, 
por exemplo, em uma empresa que oferece desconto, teríamos vários algoritmos encapsulados em 
classes concretas, herdadas da classe DescontoStrategy. A instância a ser empregada para o cálculo 
de desconto seria uma implementação desta classe em um cenário simples. Em outros, poderíamos ter 
toda uma coleção de regras de desconto, mas onde apenas uma poderia ser aplicada. Esta coleção 
pode ser implementada como uma agregação de DescontoStrategy. 
Quando o cliente requisitasse o valor do desconto para um objeto composto por várias instâncias de 
DescontoStrategy, esta instância poderia percorrer este conjunto de regras e selecionar aquela mais 
vantajosa para o cliente. Assim poderíamos ter regras como “na compra de 2 lápis você ganha 5% de 
desconto”, “se possui 60 anos ganha 10% de desconto nos produtos de limpeza” e assim por diante, 
cada   uma   delas   implementada   em   sua   própria   classe   e   selecionada   por   uma   regra   que   é   uma 
composição destas. 

Exercícios
Implemente código que satisfaz os seguintes itens.
1. O sistema de arquivo tanto no Linux quanto no Windows(r) são bem parecidos. Nestes, diretórios 
contêm arquivos e/ou diretórios. 
2. Trate   arquivo   como   elemento   simples   (classe  Arquivo)   e   diretório   como   elemento   composto 
(classe Diretorio) conforme o padrão Composite. Estas classes, ambas, devem herdar da classe 
abstrata Elemento, que contém a operação long tamanho(). Esta operação retorna o tamanho em 
bytes do arquivo ou diretório correspondente. No cado de um diretório, o tamanho é dado pela 
soma dos tamanhos de todos os arquivos contidos no diretório e eventuais subdiretórios.
3. Crie um programa que receba como argumento um nome de um arquivo ou diretório. Por exemplo, 
texto.txt ou /home/user/fulano. No primeiro caso o programa deverá retornar o tamanho em bytes 
do arquivo fornecido. No segundo caso, o tamanho em bytes de todos os arquivos contidos no 
diretório fornecido. O diretório pode conter subdiretórios e estes outros e assim por diante. Todos 
os arquivos no  diretório   e  eventuais subdiretórios devem ser computados.  Se  não   for possível 
“contar” o tamanho em bytes, então exiba a mensagem “Não foi possível computador o tamanho 
de <arquivo­fornecido­como­arg>”. Isto pode ocorrer, por exemplo, por falta de permissão. 
4. Deverá   ser   criada   uma   instância   de  Elemento  correspondente   ao   argumento   fornecido   ao 
programa através do emprego do padrão Factory, via classe  ElementoFactory. Caso o usuário 
forneça como argumento o nome de um arquivo, então uma única instância de Arquivo deverá ser 
criada   por  ElementoFactory.   Caso   o   usuário   forneça   um   diretório,   então   uma   instância   de 
Diretorio deverá ser criada. Uma instância de Diretorio deverá agrupar todas as instâncias de 
Arquivo  e/ou  Diretorio  que   se   fizerem   necessárias   para   refletir   a   organização   do   diretório 
fornecido.   Em  qualquer  um  dos  casos,  só   após  obtida  a  instância  de   Arquivo  ou  Diretorio
correspondente ao argumento fornecido, envie a mensagem tamanho().
5. Tanto um arquivo quanto um diretório, em Java, podem ser manipulados através de uma instância 
da  classe  java.io.File.   A  forma  new File(“arq.txt”)  cria  uma  instância para  o  arquivo 
fornecido   como   argumento   enquanto  new File(“/home/user/fulano”)  para   o   diretório 
fornecido. Se f é uma instância de java.io.File, sabe­se que f refere­se a um arquivo ou a um 
diretório através dos métodos isFile() e isDirectory(), respectivamente. Ambos retornam um 
valor do tipo boolean.
6. Dois métodos relevantes desta classe, para os interesses deste exercício são long length(), que 
retorna o tamanho do arquivo correspondente à instância que recebe esta mensagem, e  File[]
listFiles(), que retorna todos os arquivos contidos no diretório em questão. Observe que o 
primeiro   destes   métodos   só   faz   sentido   quando   aplicado   a   uma   instância   de   File  que   faz 
referência a um arquivo, enquanto o segundo método só se aplica a uma instância de  File que faz 
referência a um diretório. 

27 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Decorator (estrutura)
O padrão Decorator adiciona uma camada de funcionalidade a uma classe sem o emprego de herança. 
A classe resultante decora a classe original com alguma funcionalidade não disponível anteriormente. 
Observe a classe Pessoa fornecida abaixo.

package decorator;

public class Pessoa {


private String nome;
private String codigo;

public String getCodigo() {


return codigo;
}

public void setCodigo(String codigo) {


this.codigo = codigo;
}

public String getNome() {


return nome;
}

public void setNome(String nome) {


this.nome = nome;
}

public String toString() {


return nome + " " + codigo;
}
}

É provável que o estado de uma instância desta classe tenha que ser enviado para um arquivo texto, 
não no formato retornado pelo método toString(), mas empregando valores separados por vírgulas, 
ou seja, o formato CSV (Comma Separated Value). Em sentido contrário, você pode desejar inclusive 
que instâncias de Pessoa possam ser criadas através de arquivos neste formado. 
Para contemplar estas novas funcionalidades várias opções estão disponíveis. Uma delas é criar a 
classe  PessoaCSV,   que   herda   da   classe  Pessoa  e   implementa   métodos   como  toCSV()  e 
fromCSV(String).  Além da minha falta de habilidade na identificação de um nome mais aprazível, 
talvez toda a aplicação tenha que ser recompilada após as mudanças correspondentes serem aplicadas. 
Da perspectiva do controle de qualidade esta opção pode ser inaceitável.

Os   métodos  toCSV()   e  fromCSV(String)  provavelmente   terão   que   ser   sobrecarregados   pelos 


métodos toCSV(OutputStream) e fromCSV(InputStream). O objetivo é permitir que a nova classe 
PessoaCSV consiga obter um objeto de um arquivo e, de forma análoga, enviar esta instância para um 
arquivo. Neste ponto, por mais que me esforce, não encontro um identificador adequado para esta 
nova classe, que deve herdar de PessoaCSV. Simplesmente acrescentar todos estes métodos à classe 
Pessoa diminui a coesão, pois questões do domínio estão implementadas lado a lado com questões 
tecnológicas,   onde   o   princípio   conhecido   por  separation   of   concerns  parece   simplesmente   ser 
ignorado. 

28 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

O cenário acima ainda pode ser tratado pelo emprego do padrão Decorator. As funcionalidades que 
foram acrescentadas são mantidas em classes que não herdam da classe Pessoa. Podemos continuar 
com a classe  PessoaCSV, que agora não herda de  Pessoa, mas é um decorador de  Pessoa. Esta 
abordagem é ilustrada abaixo.

A implementação deste modelo é fornecida abaixo. Convém ressaltar que, nesta abordagem, métodos 
que   implementam   necessidades   de   software   (tecnológicas)   foram   isolados   daqueles   que   oferecem 
semântica para o domínio em questão.

package decorator;

import java.util.StringTokenizer;

public class PessoaDecorator {


private Pessoa p;

public PessoaDecorator(Pessoa p) {
this.p = p;
}

public String toCSV() {


return p.getNome() + ", " + p.getCodigo();
}

public void fromCSV(String csv) {


StringTokenizer s = new StringTokenizer(csv,",");
p.setNome(s.nextToken());
p.setCodigo(s.nextToken().trim());
}
}

Observe que esta classe possui um único construtor que recebe como argumento uma instância de 
Pessoa. O método  toCSV()  é trivial, simplesmente obtém os elementos que compõem o estado do 
objeto  Pessoa  e   os   agrupa   em   uma   seqüência   de   caracteres   no   formato   CSV.   O   método 
fromCSV(String) faz o oposto. Da seqüência de caracteres fornecida são obtidos os elementos que 
compõem o estado   do objeto  Pessoa  e, posteriormente, um a um são utilizados para atualizar o 
estado deste objeto.  O código abaixo permite testar esta funcionalidade. 

package decorator;

import junit.framework.TestCase;

public class PessoaDecoratorTest extends TestCase {

public void testSimples() {


Pessoa p1 = new Pessoa();
p1.setNome("João do Santos Futebol Clube");
p1.setCodigo("SFC");

29 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

PessoaDecorator dp1 = new PessoaDecorator(p1);

Pessoa p2 = new Pessoa();


PessoaDecorator dp2 = new PessoaDecorator(p2);
dp2.fromCSV(dp1.toCSV());

assertEquals("João do Santos Futebol Clube", p2.getNome());


assertEquals("SFC", p2.getCodigo());
}
}

O   emprego   do   padrão   Decorator   resultou   em   duas   classes   coesas,   PessoaDecorator  e  Pessoa. 


Agora é possível converter o estado de uma instância de Pessoa para o formato CSV e, no sentido 
inverso, definir o estado de uma instância a partir deste formato. Falta tratar estas operações a partir de 
um arquivo e armazená­las em um arquivo. 
Para   implementar   a   funcionalidade   que   ainda   falta,   um   outro   decorador   pode   ser   empregado: 
CSVFileStream. Como o nome sugere, este decorador é responsável por ler e escrever arquivos no 
formato CSV. Um benefício deste decorador é que este pode ser empregado não apenas para a classe 
PessoaDecorator, mas para todo e qualquer objeto que precise ser representado neste formato. Para 
viabilizar  esta   proposta   é  necessário   que  toda   classe   cujas  instâncias   poderão   ter  os  seus  estados 
obtidos e fornecidos no formato CSV implementem a interface abaixo. 

public interface CSV {


String toCSV();
void fromCSV(String);
}

Observe que a classe PessoaDecorator terá que ser alterada. A mudança é simples, pois esta classe 
já implementa a interface CSV, falta apenas explicitar isto na declaração da classe conforme abaixo.

public class PessoaDecorator implements CSV {


// ...
}

O diagrama correspondente é fornecido abaixo.

A classe CSVFileDecorator tem um construtor com dois argumentos: o nome de um arquivo e um 
objeto. Será a partir do arquivo que uma linha CSV será obtida e para o qual uma linha CSV será 
escrita. O objeto implementa a interface  CSV. Qualquer objeto de uma classe que implementa esta 
interface poderá ser fornecido. O conteúdo da linha CSV lida será depositado no estado do objeto 
fornecido ou, conforme a requisição, será deste objeto o estado a ser convertido em uma linha CSV e 
armazenada no arquivo fornecido. Esta classe é fornecida abaixo.

package decorator;

import java.io.File;

30 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;

public class CSVFileDecorator {


private String nomeArquivo;
private CSV csv;

public CSVFileDecorator(String nomeArquivo, CSV csv) {


this.nomeArquivo = nomeArquivo;
this.csv = csv;
}

public boolean fromFile() {


try {
Scanner s = new Scanner(new File(nomeArquivo));
csv.fromCSV(s.nextLine());
s.close();
} catch (FileNotFoundException e) {
return false;
}
return true;
}

public boolean toFile() {


FileWriter fw = null;
try {
fw = new FileWriter(nomeArquivo);
fw.write(csv.toCSV());
} catch (IOException e) {
return false;
} finally {
try {
if (fw != null) {
fw.close();
}
} catch (Exception e) {
return false;
}
}
return true;
}
}

Um simples teste é fornecido abaixo.

package decorator;

import junit.framework.TestCase;

public class DecoratorTest extends TestCase {

public void testSimples() {


Pessoa p1 = new Pessoa();
p1.setNome("João do Santos Futebol Clube");
p1.setCodigo("SFC");
PessoaDecorator dp1 = new PessoaDecorator(p1);

// Armazena pessoa 'p1' em 'saida.txt' no formato CSV


CSVFileDecorator decorator = new CSVFileDecorator("saida.txt", dp1);
decorator.toFile();

// Recupera, em outra instância, estado armazenado acima


Pessoa p2 = new Pessoa();
PessoaDecorator dp2 = new PessoaDecorator(p2);
decorator = new CSVFileDecorator("saida.txt", dp2);

31 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

decorator.fromFile();

assertEquals("João do Santos Futebol Clube", p2.getNome());


assertEquals("SFC", p2.getCodigo());
}
}

A   biblioteca   Java   também   faz   uso   extensivo   do   padrão   Decorator.   As   classe  FileReader  e 
FileWriter,   por   exemplo,   em   geral   são   empregadas   decoradas,   respectivamente,   pelas   classes 
BufferedReader e BufferedWriter. Todas estas classes pertencem ao pacote java.io. Observe que 
BufferedReader oferece forma de leitura por meio do emprego de um buffer, o que não é fornecido 
pela  classe  FileReader.   A   funcionalidade   é   similar,   embora   o   ganho   em   desempenho   possa  ser 
significativo. O mesmo ocorre com a classe BufferedWriter, que não escreve imediatamente após 
cada requisição (conforme ocorre com a classe FileWriter), mas em um buffer antes que a escrita de 
todo o  buffer  seja realizada. Apenas por precaução, é desnecessário  afirmar que o  Decorator não 
necessariamente está associado a desempenho, conforme exemplo anterior.
No exemplo abaixo, o método main explicitamente faz uso do padrão Decorator ao criar a instância de 
BuffereWriter, que decora a classe FileWriter. 

 
package decorator;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;

interface Operacao {
void processa(int[] arrayInt) throws Exception;
}

class UsaWriter implements Operacao {


private String fmt = "%2d %2d %2d %2d %2d %2d\n";
private Writer writer;
public UsaWriter(Writer writer) throws Exception {
this.writer = writer;
}

public void processa(int[] d) throws Exception {


String jogo = String.format(fmt, d[1], d[2], d[3], d[4], d[5], d[6]);
writer.write(jogo);
}

public void termina() throws Exception {


writer.close();
}
}

public class MegaSena {


private int[] v = new int[] { 0, 0, 0, 0, 0, 0, 0 };
private Operacao operacao;

public MegaSena(Operacao operacao) {


this.operacao = operacao;
}

// Gera 50063860 jogos possíveis da megasena


private void gera(int k) throws Exception {
for (v[k] = v[k - 1] + 1; v[k] <= 60; v[k]++) {
if (k == 6) {
operacao.processa(v);
} else {
gera(k + 1);
}
}
v[k]--;
}

32 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

public static void main(String[] args) throws Exception {


Writer fw = new FileWriter(new File("/tmp/fw.txt"));
new MegaSena(new UsaWriter(fw)).gera(1);
fw.close();

Writer bw = new BufferedWriter(new FileWriter(new File("/tmp/bw.txt")));


new MegaSena(new UsaWriter(bw)).gera(1);
bw.close();
}
}

Exercícios
Uma aplicação que faça uso da tecnologia de Servlets cria, em geral, uma classe que trata requisições 
oriundas de uma determinada página. Em um extremo podemos ter uma classe (um Servlet) para cada 
página   existente   no   sistema.   Esta   pode   ser   uma   opção   onerosa.   A   análise   da   interface 
javax.servlet.ServeletRequest  revela que um  java.util.Map  é empregado  para encapsular 
uma requisição. Para evitarmos um considerável número de Servlets, uma alternativa é empregar um 
único   Servlet   que   recebe   requisições   e   as   encaminha   para   classes   que   não   necessariamente   são 
Servlets,   mas   que   oferecem   os   serviços   requisitados.   Uma   abordagem   “similar”   é   realizada   por 
serviços web, onde as requisições são empacotadas em documentos XML. 
Este   exercício   sugere   que   o   padrão   Decorator   “decore”   um  java.util.Map  onde   as     restrições 
fornecidas abaixo devem ser observadas. Convém ressaltar que uma simples classe que realize os itens 
(veja abaixo) talvez seja mais adequado que o emprego de um Decorator. Contudo, esta é apenas uma 
simplificação de propósito didático. Conforme o exemplo empregando Servlets, em um cenário real 
estaremos lidando com um Servlet e a adaptação da implementação do exemplo abaixo seria simples.
1. A classe ComandoMapDecorator tem construtor  onde java.util.Map é o único argumento. 
2. No objeto recebido como argumento do construtor todas as chaves são seqüências de caracteres, 
assim como os valores correspondentes armazenados.
3. Uma   única   entrada   deve   ter   como   chave   “interface”   e  como   valor   o   nome   completo   de   uma 
interface, por exemplo, “br.ufg.inf.projeto.Servicos”.
4. Uma interface define várias operações. Para cada operação teremos pelo menos duas entradas. 
Uma com a chave “operação” e o valor correspondente ao identificador da operação. A outra 
entrada indica a quantidade de argumentos da operação. Neste caso, a chave é formada pelo nome 
da   operação   concatenada   com   a   seqüência   “.args”.   O   valor   correspondente   é   “0”   para   uma 
operação   que   não   possui   nenhum   argumento,   “1”   para   operação   com   um   argumento   e   assim 
sucessivamente.   Por   exemlo,   para   a   interface  Servicos  contendo   a   operação  getValor(),   a 
instância de  java.util.Map  correspondente deverá possuir três entradas. A saber: (“interface”, 
“Servicos”) , (“operacao”, “getValor”) e  (“getValor.args”, “0).
5. Quando uma operação possui argumentos é necessário conhecer a ordem e o tipo de cada um 
deles. Cada argumento de uma operação é fornecido em uma entrada própria, onde a chave tem 
um formato  próprio. Este formato  é dado  pelo identificador da operação  concatenada com “.” 
seguida da ordem do  argumento   para esta operação.  O  primeiro  argumento  é  de  ordem  1,    o 
segundo 2 e assim sucessivamente. Por exemplo, para a operação “sqrt(double)”, teríamos uma 
entrada correspondente cuja chave é “operação” e o valor “sqrt” e outra entrada de chave “sqrt.1” e 
valor “double”. Esta última entrada indica que o primeiro argumento da operação “sqrt” é do tipo 
double.

6. A classe que implementa o padrão Decorator possui a operação execute(Object). Quando uma 
instância desta classe recebe esta mensagem, a operação descrita no Map é executada sobre o 
objeto fornecido como argumento. 
7. Para   requisitar   dinamicamente   a   execução   de   um   método   pode­se   empregar   o   código   abaixo. 
Convém ressaltar que  clazz  é uma instância de  Class  que representa o objeto que receberá a 

33 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

mensagem;  operacao  é   o   nome   do   método   correspondente   (por   exemplo,   “getValor”);   e   os 


métodos  getTypeArgs()  e  getValueArgs()  retornam   respectivamente,  Class[]  (cujos 
elementos   são   os   tipos   dos   argumentos)   e  Object[]  (cujos   elementos   são   valores   dos 
argumentos). Consulte java.lang.reflect.Method e java.lang.Class para detalhes.
Method m = clazz.getMethod(operacao, getTypeArgs())
retorno = m.invoke(obj, getValueArgs());

Façade (estrutura)
Um contador ao preencher o imposto de renda é uma forma de Façade, onde toda a parafernália da 
contabilidade é ocultada do contribuinte por este prestador de serviços. Para o contribuinte, quando se 
contrata um contador, não é necessário, embora conveniente, o domínio de expressões de cálculo, leis 
e outros elementos relevantes para o preenchimento da declaração. O contador é um exemplo do 
emprego do padrão Façade para a contabilidade. 
De forma análoga temos um garçon em um restaurante. Felizmente, um jantar a dois em um lugar 
romântico    não  exige  habilidades culinárias do  casal. Há  um Façade  atento para  um  conjunto  de 
mesas, o garçon, que se encarregará de tornar o jantar tão simples quanto requisitar e aguardar um 
pouco. Isto depende do local, claro!
Quando   se   tem   um   subsistema   que   oferece   considerável   quantidade   de   serviços   e   apenas   um 
subconjunto deles é desejado, pode­se encapsular o subsistema em uma interface mais simples. Da 
perspectiva do cliente apenas a interface terá que ser dominada, em vez de todo o subsistema. Este 
encapsulamento é realizado por um Façade. 
O padrão Façade é implementado como um intermediário entre o cliente e um subsistema. Além de 
facilitar o uso, conforme apresentado acima, este padrão também permite maior independência do 
cliente em relação ao subsistema. Quando um concorrente oferecer uma versão mais eficiente, você 
não terá espalhado por toda a sua aplicação a API do subsistema anterior, que foi encapsulado pelo 
Façade. A figura abaixo ilustra o padrão Façade. 

<<subsystem>>
Complexo
A
ComplexoFacade
­ instance
Cliente B
+ metodo1()
+ metodo2()
+ getInstance() Z

Por simplicidade o modelo acima emprega uma subsistema identificado por Complexo e composto por 
três classes. Adicionalmente foi acrescentada a classe  ComplexoFacade, que implementa o padrão 
Façade. Observe que esta classe é parte do  subsistema. Em conseqüência, a classe  Cliente, que 
abstratamente representa o restante do código que faz uso dos serviços oferecidos pelo subsistema não 
precisa  conhecer  os  detalhes deste  subsistema. Isto  reduz o   acoplamento   geral da aplicação.  Esta 
redução também favorece a substituição do subsistema por outro, pois a dependência do restante da 
aplicação está localizada apenas na classe ComplexoFacade.
Embora   a  ilutração   acima   tenha  empregado   uma  única   classe   como   ponto   de   entrada   a  todo   um 
subsistema, em cenários reais podem ser necessárias várias classes. Por exemplo, a API JDBC pode 
ser vista como uma aplicação do padrão Façade composta por dezenas de classes e interfaces. Sem 

34 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

esta API, toda aplicação em Java teria que conhecer e fazer uso de detalhes específicos da interface 
oferecida pelo SGBD empregado.

Proxy (estrutura)
Em   português,   o   termo  proxy  sugere   algo   como   representante,   procurador.   No   nosso   contexto, 
felizmente, o  termo é sugestivo. Quando se emprega o padrão Proxy, sabe­se que um objeto torna­se 
representante de outro. 
Esta representação é desejada quando, por exemplo, o objeto principal, aquele representado, possui 
processo de criação oneroso e o acesso a ele deve ser controlado, entre outros casos. O representante 
ou objeto Proxy age como um intermediário, como um procurador do objeto principal. Só se tem 
acesso ao objeto principal pelo seu Proxy, o que permite o controle de acesso ao objeto principal, por 
exemplo. 
O famoso OpenOffice (http://www.openoffice.org/) compreende um editor de textos que ao abrir um 
documento com figuras, não as exibe imediatamente, sem impedir que o usuário interaja com o texto 
até que todas as figuras tenham sido carregadas e exibidas, o que pode ser um processo demorado 
demais para um usuário. Uma resposta imediata pode ser obtida através do emprego do padrão Proxy. 
Cada objeto figura possui um objeto Proxy correspondente. Quando o documento é aberto são os 
objetos Proxy que irão responder pelos objetos que estes representam. Um comportamento possível é 
retornar um simples quadro com o nome da figura. Esta resposta rápida pode ser visualizada pelo 
usuário imediatamente enquanto o objeto Proxy realiza a carga da figura. Ao término, a figura real 
substitui   o   quadro   com   o   nome   fornecido   temporariamente.   Navegadores   (browsers)   também 
apresentam comportamento parecido. 
Observe que se o texto é extenso e contém várias figuras, pode ser que um objeto Proxy nem mesmo 
inicie o processo de carga imediatamente, mas apenas quando o usuário se deter em uma página na 
qual uma figura deve aparecer. Neste exemplo, o objeto Proxy foi empregado como intermediário 
com o propósito de melhorar o desempenho da perspectiva do usuário. 
A GoF ilustra outros empregos deste padrão. Um objeto Proxy pode representar localmente um objeto 
remoto. Para os clientes, o objeto de interesse parece disponível localmente. O objeto Proxy torna­se 
responsável por requisitar remotamente os serviços requisitados localmente. Um objeto Proxy pode 
representar um objeto cujo processo de criação, conforme comentado nos parágrafos anterior, é lento 
e, portanto, a ser realizado sob demanda apenas quando necessário. Um objeto Proxy pode proteger o 
objeto principal de tal forma que toda mensagem tem a origem validada antes de chegar ao destino. 
Um objeto Proxy também pode implementar um forma mais sofisticada de referência, onde um objeto 
é carregado do meio persistente apenas quando uma mensagem que exige a disponibilidade do objeto 
referenciado for enviada. Isto permite, por exemplo, o tratamento de extensas coleções de objetos em 
memória. O código abaixo ilustra este caso onde cada objeto  Texto  representa um arquivo texto. 
Observe que o arquivo correspondente é carregado toda vez que o nome do arquivo é alterado. 

import java.io.FileInputStream;
import java.util.Scanner;

public class Texto {


protected String nome = "default.txt";
protected String conteudo = null;

public void setNome(String arquivo) {


nome = arquivo != null ? arquivo : nome;
carregaArquivo();
}

public void carregaArquivo() {


try {
FileInputStream fis = new FileInputStream(nome);

35 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Scanner leitor = new Scanner(fis);


conteudo = "";
while (leitor.hasNextLine()) {
conteudo += leitor.nextLine() + "\n";
}
} catch (Exception e) {
System.out.println("ERRO");
}
}

public String toString() {


return conteudo;
}

public String getConteudo() {


return conteudo;
}
}

Ao   contrário   da   versão   acima,   a   classe  TextoProxy  só   irá   realizar   a   carga   do   arquivo   se   for 
necessário, e no último momento, o que é conhecido por  lazy initialization. Este último instante é 
detectado através do único método da classe que exige a carga do arquivo, o método   toString(). 
Esta classe é derivada da classe Texto, ou seja, da perspectiva do cliente da classe Texto, tanto uma 
instância desta quanto da classe TextoProxy pode ser fornecida, sem a necessidade do conhecimento 
do cliente. O método setNome(String) também foi alterado.

public class TextoProxy extends Texto {


protected String nome = "default.txt";
protected String conteudo = null;

public void setNome(String arquivo) {


nome = arquivo != null ? arquivo : nome;
}

public String toString() {


if (conteudo == null) {
carregaArquivo();
}
return conteudo;
}

public String getConteudo() { // Deixado assim apenas para teste!


return conteudo;
}
}

Um   teste   de   unidade   é   fornecido   abaixo.   Um   objeto  Texto  e   outro  TextoProxy  são   criados.   O 
primeiro carrega o arquivo correspondente toda vez que o método  setNome(String) é chamado. O 
segundo só irá carregar o arquivo se for preciso. Neste exemplo, a última linha do código abaixo exige 
a carga do arquivo, pois este deve ser impresso. 

import junit.framework.TestCase;

public class TextoProxyTest extends TestCase {

public void testCasoSimples() {


Texto cliente = new Texto();
cliente.setNome(null); // Carrega arquivo
assertNotNull(cliente.getConteudo());

cliente = new TextoProxy();


cliente.setNome(null); // Não carrega arquivo
assertNull(cliente.getConteudo());
}
}

36 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Interpreter (estrutura)
O padrão Interpreter é empregado para representar sentenças em uma determinada linguagem e para 
interpretar sentenças nesta linguagem. O padrão não tem o propósito de substituir os sofisticados 
mecanismos empregados na construção de compiladores, mas pode ser empregado efetivamente para 
linguagens mais simples. 
Uma  linguagem  “simples”, por exemplo, pode ser definida  em um sistema  de informação  para a 
geração de um arquivo de saída, para definir a ordem de saída das colunas de um relatório. Pode ser 
necessário interagir com sistemas através de um formato que continuamente muda e, neste caso, um 
interpretador pode ser definido para facilitar a geração de um leitor de arquivos neste formato volátil. 
Expressões matemáticas simples também podem ser oferecidas aos usuários de um sistema, de forma 
que este possa, por exemplo, definir qual a expressão de avaliação da média de um aluno. 
Nos casos acima e em muitos outros, o interesse pelo padrão Interpreter está no fato de permitir que 
especificações em um linguagem possam ser executadas sem a necessidade de se alterar, compilar e 
posteriormente  executar código. Um interpretador  permite que sentenças na  linguagem  subjacente 
possam ser produzidas em tempo de execução de um programa. Se for simples, então até um usuário 
final pode fornecer sentenças que são prontamente executadas. 

Se você está realmente interessado em construir a sua própria linguagem, então recomendo a leitura de 
Building Parsers with Java, Steven John Metsker, Addison­Wesley, 2001. 

Template (comportamento)
O padrão Strategy (53) permite encapsular um algoritmo em uma classe. Este padrão permite que a 
estrutura de um algoritmo seja definida por meio de métodos abstratos a serem implementados por 
subclasses. Neste caso, cada subclasse implementa o algoritmo. Ao contrário do padrão Strategy, são 
necessárias pelo menos duas classes por algoritmo: uma que define a estrutura invariável e outra que 
fornece os métodos empregados por esta estrutura. 
A classe abstrata  Autenticacao  fornecida abaixo, por exemplo, faz uso do padrão Template. Esta 
classe abstrata encapsula o fluxo de autenticação de um usuário. Este encapsulamento é realizado pelo 
método validaIdentidade(). Quando executado, este método primeiro irá obter a identidade a ser 
avaliada. Isto é abstratamente realizado por meio do método  obtemIdentidade(). Em seguida é 
executado o processo de validação da identidade obtida. Novamente, não é especificado como esta 
operação será realizada, o que é uma responsabilidade de uma implementação. Apenas é oferecida a 
oportunidade de validar a identidade obtida pela ação anterior. Conforme o resultado desta validação, 
uma mensagem de sucesso ou erro é informada. A mensagem pode ser um simples texto exibido na 
saída padrão, o acréscimo de um registro em arquivo de log ou nenhuma operação sequer. Isto porque 
tais detalhes não são fornecidos na classe abstrata, mas são de responsabilidade da classe que estende 
esta classe abstrata. 

public abstract class Autenticacao {


public final boolean validaIdentidade() {
Identidade i = obtemIdentidade(); // Requisita nome e senha
if (Negocio.getSegurancaInstance().verifica(i)) {
exibeSucesso(i);
} else {
exibeErro(i);
}
}

public abstract Identidade obtemIdentidade();


public abstract boolean validaIdentidade(Identidade i);
public abstract void exibeSucesso(Identidade i);

37 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

public abstract void exibeErro(Identidade i);


}

Observe que a classe Autenticacao é abstrata. Apenas uma subclasse concreta, que implemente os 
métodos abstratos poderá dar origem a instâncias e, em particular, implementar o algoritmo que valida 
a   identidade   de   um   usuário.   O   algoritmo   neste   caso   é   trivial,   o   primeiro   método, 
obtemIdentidade(),   deverá   retornar   uma   instância   de   Identidade,   que   corresponde   ao   nome   do 
usuário e a sua senha. A classe  AutenticacaoGUI  provavelmente empregará uma caixa de diálogo 
para  obter  estas  informações,  enquanto  a  classe  AutenticacaoConsole  irá empregar  a entrada  e 
saída padrãos. Ambas as implementações estarão disponíveis no método   obtemIdentidade(). De 
forma análoga cada um dos métodos abstratos deverão ser implementados. 
Convém ressaltar que a classe concreta não herda apenas um conjunto de operações abstratas, como é 
típico com o padrão Strategy. Neste caso, uma classe derivada de Autenticacao também herda um 
algoritmo, cuja estrutura, a implementação empregando métodos abstratos, foi fornecida e não pode 
ser   alterada,   observe   o   emprego   do   modificador  final.   Qualquer   classe   derivada   deve   apenas 
preencher os espaços vazios, implementar os métodos abstratos. 
Embora este exemplo seja simples, é fácil constatar o emprego do padrão Template. Desenvolvedores 
web fazem uso extensivo da classe  javax.servlet.http.HttpServlet, uma classe abstrata que 
contém o método service(ServletRequest req. ServletResponse resp), entre vários outros. 
Raramente   este   método   é   sobrescrito.   Classes   derivadas   de  HttpServlet  reutilizam   a   elaborada 
implementação deste método, que faz uso de métodos cuja implementação deve ser fornecida nas 
classes derivadas, exatamente como o  padrão  Template. Ou  seja, apenas a estrutura do  algoritmo 
implementado neste método é reutilizada. Os detalhes são fornecidos por classes derivadas. 

Exercícios
1. Faça o modelo UML correspondente à solução empregada pela biblioteca Java para implementar o 
método sort(Object[] a) fornecido pela classe java.util.Arrays. Explique como o padrão 
Template está sendo empregado.

Chain of responsibility (comportamento)


O   padrão   Strategy   (53)   encapsula   um   algoritmo.   Imagine   que   existam   várias   implementações 
possíveis de um algoritmo, cada uma delas devidamente encapsulada em sua própria classe através do 
emprego   do   padrão   Strategy.   Um   cliente   está,   em   geral,   interessado   em   apenas   uma   destas 
implementações capaz de oferecer o serviço requisitado. Ou melhor, o cliente não depende de uma 
implementação específica, mas de alguma que ofereça o serviço requisitado. 
O padrão Chain of Responsibility pode ser usado para encadear implementações e permitir que a 
decisão   de   qual   implementação   empregar   seja   estabelecida   em   tempo   de   execução,   assim   como 
implementações podem ser dinamicamente inseridas e removidas nesta seqüência de implementações. 
Em geral, cada Strategy nesta seqüência tenta atender a requisição. Se a implementação empregada 
não puder atender a requisição, então esta é passada para a próxima implementação na cadeia. A 
próxima tem a mesma postura, até que a última possa atendê­las, ao menos é o que se espera. 
Onde   este   padrão   pode   ser   efetivamente   empregado?     Uma   aplicação   que   precisa,   via   Internet, 
entregar a um receptor remoto um determinado arquivo pode se beneficiar deste padrão. O algoritmo 
de   entrega   pode   ser   implementado   no   método  entregaArquivo(String) da   interface 
EntregaStrategy. Cada implementação deverá implementar esta interface (veja padrão Strategy). 
Uma implementação pode fazer uso do protocolo FTP.  Caso  não seja possível, podemos tentar o 
protocolo HTTP utilizado em outra implementação. Na seqüência, podemos experimentar o uso direto 
de Sockets para um receptor disponível no destino. Ainda podemos fazer uso de um e­mail específico. 

38 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Assuma que estas implementações devem ser executadas nesta ordem, caso a anterior não consiga 
oferecer o serviço. Este é um caso natural para o emprego do padrão Chain of Responsibility. 
Naturalmente, se a implementação que faz uso de FTP obter sucesso, nenhuma das outras estratégias 
implementadas e disponíveis na cadeia será executada, pois o serviço já foi prestado. Caso contrário, a 
próxima   estratégia   é   executada.   Também   é   possível   que   o   emprego   de   e­mail,   por   questões   de 
segurança, seja desabilitado por algum período e, neste caso, em tempo de execução, esta estratégia 
pode ser removida da cadeia de implementações, assim como uma nova proposta pode ser inserida. 
Como detectar um cenário onde o padrão Chain of Responsibility pode trazer benefícios? Algumas 
aplicações, confesso que também já fiz assim, implementam esta seqüência de opções em um único 
método. Inicialmente o método experimenta a solução preferida, se esta falha, então um teste conduz à 
execução da próxima implementação preferida e assim por diante. O código deste método é difícil de 
ser alterado. Coexistem, em um mesmo método, várias estratégias de solução, o que torna a coesão 
baixa. Também não permite a inserção e remoção dinâmicas de estratégias pois, neste caso, código 
deverá ser editado, compilado. A aplicação deverá ser interrompida para que a nova versão assuma o 
lugar   da   anterior.   Da   perspectiva   de   teste   temos   um   cenário   ainda   mais   indesejável,   pois   não   é 
possível testar isoladamente a nova proposta de implementação. O padrão Chain of Responsibility não 
possui nenhum destes inconvenientes. 

Exercícios
Sistemas de informação exigem, em geral, vários valores necessários para que possam ser executados 
de forma satisfatória. Faça uso do padrão Chain of Responsability para implementar uma aplicação 
que oferece a flexibilidade exibida. O programa faz uso de informações de configuração obtida de 
variáveis   de   ambiente.   Uma   variável   de   ambiente   é   obtida   através   de  String getVar(String)
(método  static)   da   classe  Configuracao.   Quando   uma   variável   não   é   encontrada,   a   aplicação 
procura   por   uma   propriedade,   que   pode   ser   fornecida   via   linha   de   comandos,   no   momento   da 
execução do programa ou durante a leitura de arquivo de propriedades. Caso esta opção falhe, então 
um arquivo de nome predefinido é consultado no diretório corrente. Caso esta última opção também 
falhe, então um arquivo é obtido via Internet (servidor predefinido). Naturalmente, esta opção pode 
resultar em falha e, neste caso, um portal alternativo, outro servidor, é procurado. Se e somente se as 
opções anteriores falharem, então um valor predefinido é empregado. Para implementar este cenário 
será útil o acesso aos serviços comentados abaixo:
1. Obtém­se o valor de uma variável de ambiente através de System.getenv(String). Se a variável 
não estiver definida, então o valor null é retornado.
2. Uma propriedade pode ser obtida com System.getProperty(String). Novamente, retorna null
caso se não existir propriedade com esta chave. Esta chamada pode gerar exceção, particularmente 
quando não houver permissão suficiente para investigar estas propriedades. Uma propriedade pode 
ser   definida   no   momento   da   chamada   da   máquina   virtual   Java,   conforme   ilustrado   abaixo

java -DpropriedadeNome=”Valor da propriedade” meupackage.MinhaClasse


3. O método load(InputStream) obtém propriedades do arquivo fornecido. Este método pertence à 
classe java.util.Properties.
4. A classe  java.util.URL  pode ser empregada na definição de um recurso. Através do método 
InputStream openStream()  pode­se   obter   o   conteúdo   de   arquivo   possivelmente   remoto, 
conforme o URL em questão.

39 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Command (comportamento)
O   padrão   Command   é   empregado   para   encapsular   uma   requisição   em   um   objeto.   Em   Java, 
argumentos  de   métodos   não   incluem  funções   como   em  C/CC++.   Se   desejarmos   que  um  método 
receba   uma   função   cuja   execução   será   requisitada   pelo   método,   então   esta   função   terá   que   ser 
implementada em uma classe cuja instância será fornecida ao método. Esta instância oferece uma 
forma de representar uma requisição.
Um cliente pode depender da execução de uma tarefa que só é fornecida em tempo de execução. 
Desta forma, quem recebe não sabe sequer quem irá oferecer o serviço e, se observarmos atentamente, 
aquele responsável pela execução, conforme a classe acima, não sabe quem requisitou. Este é um dos 
empregos do padrão Command. 
Este padrão também permite desacoplar o sincronismo entre uma requisição e a execução imediata da 
requisição. Todas as instâncias de classes que implementam a interface  Command podem ser guardadas 
em uma lista para posterior execução, talvez quando mais recursos estiverem disponíveis. Este pode 
ser, inclusive, um mecanismo utilizado para a distribuição de carga entre processadores. 
Estes   empregos   também   sugerem   um   outro:   reversão   dos   efeitos   de   uma   operação   (undo).   Uma 
interface como aquela abaixo seja empregada para encapsular o conjunto de requisições de interesse.

public interface Comando {


void faz();
void desfaz();
}

Para ilustrar, suponha que desejamos encapsular a requisição que altera o endereço de uma pessoa, 
representada   por   uma   instância   da   classe  Pessoa,   fornecida   abaixo.   Por   simplicidade,   apenas   o 
atributo endereco e métodos correspondentes foram fornecidos.
public class Pessoa {
private String endereco = null;

public void setEndereco(String novoEndereco) {


endereco = novoEndereco;
}

public String getEndereco() {


return endereco;
}
}

Um cliente que faz uso desta classe deseja representar a requisição de alteração de endereço. Conforme 
visto anteriormente, o objetivo é permitir que esta requisição seja enfileirada efacilitar a operação que 
desfaz a alteração de endereço, por exemplo. Esta requisição é implementada na classe abaixo.

public class AlteraEnderecoPessoaCommand implements Comando {


private Pessoa p = null;
private String endAnterior = null;
private String novoEnd = null;

public void setPessoa(Pessoa p) {


this.p = p;
}

public void setNovoEnd(String novoEnd) {


this.novoEnd = novoEnd;
}

40 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

public void faz() {


endAnterior = p.getEndereco();
p.setEndereco(novoEnd);
}

public void desfaz() {


setNovoEnd(endAnterior);
faz();
}
}

A implementação fornecida acima exige a instância de Pessoa sobre a qual a operação será executada 
e o valor do novo endereço a ser estabelecido para esta instância. Isto permite, por exemplo, que a 
requisição   possa   ser   enfileirada   para   execução   posterior.   Também   é   armazenado   o   endereço 
imediatamente antes de uma alteração. É esta informação que permite recuperar (undo) o estado da 
instância   caso   seja   necessário   desfazer   a   requisição.   O   programa   abaixo   ilustra   o   emprego   desta 
classe.
import junit.framework.TestCase;

public class ComandoTest extends TestCase {


public void testFazDesfaz() {
Pessoa p = new Pessoa();
p.setEndereco(“Rua das Vitórias, 143”);

AlteraEnderecoPessoa cmd = new AlteraEnderecoPessoa();


cmd.setNovoEnd(“Rua Vila Nova, 150”);
cmd.setPessoa(p);

assertEquals(“Rua das Vitórias, 143”, p.getEndereco()); // Neurose

cmd.faz();
assertEquals(“Rua Vila Nova, 150”, p.getEndereco());

cmd.desfaz();
assertEquals(“Rua das Vitórias, 143”, p.getEndereco());
}
}

É muito fácil confundir o padrão Command com o padrão Strategy. O padrão Command, contudo, 
enfatiza o isolamento entre quem requisita e quem oferece um serviço. Serviço este que será oferecido 
por um objeto, talvez empregando o padrão Strategy. Ao realizar este isolamento, vários benefícios 
podem ser obtidos conforme visto acima. O emprego do padrão Strategy, por outro lado, não assegura 
tais benefícios. O padrão Strategy, por exemplo, não visa facilitar a reversão de uma operação (undo). 
O padrão Composite pode ser empregado para agrupar estratégias via o padrão Strategy, contudo, não 
teria  o  efeito   de   uma  macro   quando   o   que   é  agrupado   são   objetos  que  implementam   a  interface 
Command. 

O padrão Strategy isola um algoritmo em uma classe. O foco é o algoritmo. O padrão Command 
encapsula uma requisição propriamente dita, que será executada por um algoritmo. Estas requisições 
podem ser agrupadas em uma fila, por exemplo. O foco é a requisição. Ambos fazem uso de uma 
interface. No padrão Strategy a implementação da interface é um algoritmo. No padrão Command a 
implementação da interface encapsula uma requisição.  Da perspectiva de implementação, contudo, a 
diferença praticamente não  existe,  embora  visem  propósitos distintos  e empregos  correspondentes 
também distintos. 

Exercícios
Faça uso do padrão Command de forma que as seguintes restrições são satisfeitas.
1. A   requisição   correspondente   deverá   ser   executada   quando   a   operação  void faca()  for 
requisitada.

41 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

2. O resultado de uma requisição deverá ser desfeito se a operação void desfaca() for executada. 
Convém ressaltar que nem todas operações são reversíveis.
3. Crie   pelo   menos   duas   classes   que   implementam   a   interface  Command,   cujas   operações 
correspondentes foram definidas no item acima.
4. Crie   a   classe  ProcessadorCommand  que   executa   uma   fila   de   requisições,   desde   a   requisição 
corrente   até   a   última.   Esta   classe   também   permite   operações   do   tipo   void faz(),   onde   a 
requisição   corrente   é   executada,  void refaz()  onde   a   requisição   anteriormente   executada   é 
reexecutada e void desfaz() onde a última requisição executada é desfeita.
5. A classe ProcessadorCommand também deverá ser passível de ser serializada e, em conseqüência, 
uma determinada fila pode ser reexecutada, assim como o recurso de macros em editores de texto. 
O  método  boolean salvar(String nomeArquivo)  deverá  ser empregado  para armazenar o 
conteúdo de uma fila de instâncias de Command em um arquivo e, para obter tais instâncias de uma 
arquivo, use boolean carrega(String nomeArquivo).

Iterator (comportamento)
O   padrão   Iterator   permite   iterar   por   elementos   de   uma   coleção   de   dados   sem   que   se   tenha 
conhecimento dos detalhes de implementação destes dados. É comum o emprego deste padrão em 
bibliotecas que implementam estruturas de dados. Java possui a interface  Iterator<E> e a interface 
Enumeration<E>  ambas   disponíveis   no   pacote  java.util.   Java   também   possui   a   interface 
java.lang.Iterable<T>, que contém uma única operação (Iterator<T> iterator()).

A   interface  Iterator<E>  inclui   as   operações  boolean  hasNext()  e  E next().   Ainda  possui   a 


operação void remove(), mas esta última é de implementação opcional. A primeira das operações 
deve retornar true se e somente se existe mais elementos a serem percorridos. A segunda operação 
retorna   o   próximo   objeto   disponível   na   coleção   de   objetos   considerada.   As   duas   operações   de 
Enumeration  são  hasMoreElements()  e  nextElement()  oferecem   serviços   similares   aos   de 
Iterator. A documentação sugere que seja empregado o  Iterator, preferencialmente, em vez de 
Enumeration.

O pequeno programa abaixo ilustra o emprego do padrão Iterator. De uma lista é obtido o objeto que 
implementa a interface Iterator empregado para percorrer a lista. 

package iterator;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class SimplesUso {


public static void main(String[] args) {
List<String> lista = Arrays.asList(args);

Iterator i = lista.iterator();
while (i.hasNext())
System.out.println(i.next());

// Mesmo efeito do laço acima


for (String str : lista)
System.out.println(str);
}
}

O   exemplo   acima   faz   uso   dos   recursos   disponibilizados   pela   biblioteca   Java.   O   padrão   Iterator, 
contudo, não está restrito a tais casos. Ao construir uma estrutura de dados é comum permitir que 
clientes desta estrutura possam iterar pelos elementos, conforme o exemplo abaixo. 

42 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

package iterator;

import java.util.Iterator;
import java.util.NoSuchElementException;

class Elemento { // Elemento da estrutura de dados


String nome; // Uso didático
Elemento proximo; // Cria lista de elementos
}

class MinhaEstruturaDeDados implements Iterable<Elemento> {


Elemento primeiro;

private class MeuIterator implements Iterator<Elemento> {


private Elemento corrente = primeiro;

public boolean hasNext() {


return corrente != null;
}

public Elemento next() {


if (!hasNext())
throw new NoSuchElementException();
Elemento retorno = corrente;
corrente = corrente.proximo;
return retorno;
}

public void remove() {


throw new UnsupportedOperationException();
}
}

public Iterator<Elemento> iterator() {


return new MeuIterator();
}
}

public class MeuIterator {


public static void main(String[] args) {
MinhaEstruturaDeDados medd = new MinhaEstruturaDeDados();
Elemento elemento = new Elemento();
elemento.nome = "1";
medd.primeiro = elemento;
for (int i = 2; i < 6; i++) {
elemento.proximo = new Elemento();
elemento.proximo.nome = Integer.toString(i);
elemento = elemento.proximo;
}

for (Elemento item : medd)


System.out.println(item.nome);
}
}

Exercícios
1. Escreva uma aplicação que recebe, via linha de comandos, quando iniciada, uma seqüência de 
caracteres. A saída da aplicação deverá ser a impressão de todas as letras contidas na seqüência 
seguida da quantidade de vezes que a letra aparece na seqüência. A  impressão deverá ser em 
ordem   decrescente   da   quantidade   de   aparições.   Se   mais   de   uma   letra   aparecer   uma   mesma 
quantidade   de   vezes,   então   tais   letras   deverão   aparecer   em   ordem   alfabética.   Assim,   para   a 
seqüência, “carro”, teremos a letra r com duas ocorrências seguida, posteriormente, por três outras 
linhas. A primeira linha contendo a letra a, a segunda contendo a letra c e a terceira contendo a 
letra o.   Este exercícios deverá, necessariamente, fazer uso do padrão Iterator. Não é necessário 
implementá­lo, mas empregá­lo. 
2. Implemente o trecho de projeto abaixo.

43 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

3. Escreva a classe  Texto  com um único construtor:  Texto(URL). Esta classe deve implementar a 


interface  java.util.Iterable<String>.   O Iterator correspondente retorna uma  String  para 
cada linha do texto cujo URL foi identificado pelo construtor.  Isto deve ser de tal forma que, para 
a instância texto de Texto, para um URL válido, o código a seguir exibe, na saída padrão, todo o 
conteúdo do texto: for (String linha : texto) System.out.println(linha);

Mediator (comportamento)
Software   orientado   a   objeto   envolve   uma   coleção   de   objetos   que   trocam   mensagens   entre   eles. 
Conforme a GoF destaca, em um caso extremo, todo objeto deve conhecer todos os demais. Isto 
aumenta o acoplamento, que conduz a uma significativa dificuldade de manutenção. No caso extremo 
citado, qualquer alteração deveria ser seguida de uma análise em todos os demais objetos do software, 
o que seria impraticável para sistemas maiores. 
O padrão Command encapsula uma requisição, ou o envio de uma mensagem de um determinado 
objeto para outro sem que estes interlocutores conheçam um ao outro, o que reduz o acoplamento 
entre estes. Contudo, o emprego deste padrão tem o propósito de oferecer algumas facilidades como 
macros e undo, em vez de reduzir este acoplamento. Para reduzir o acoplamento o padrão Mediator é 
uma proposta.
O   padrão   Mediator   implementa   o   papel   de   intermediário   à   semelhança   dos   padrões   Adapter   e 
Command, por exemplo. Ao contrário destes, contudo, o papel de intermediário tem como propósito 
manter os vários interlocutores, e as complexas interações entre eles, sem que estes conheçam uns aos 
outros. Em conseqüência, a dependência entre eles é reduzida e a possibilidade de reutilização das 
classes correspondentes é maior.
O foco do padrão Mediator são as complexas interações entre objetos, encapsuladas em um objeto da 
classe Mediator. Dado um conjunto de objetos onde a troca de mensagens entre eles é complexa, o 
que pode ser comprovado pelo fato de que praticamente todo objeto deste conjunto conhece e depende 
da existência dos demais, a idéia é concentrar a troca de mensagens na classe  Mediator. Dessa forma, 
a dependência entre os os objetos deste conjunto é substituída pela dependência entre estes objetos e o 
objeto   Mediator,   que   atuará   como   intermediário,   sem   que   os   seus   interlocutores   dependam 
diretamente uns dos outros. 

Exercícios
Faça uso do padrão Mediator para reduzir o acomplamento entre as classes que possuem o prefixo C.
1. Uma instância de  CCasa, ao receber a mensagem  acende(), envia a mensagem  acendeLuz()
para as instâncias de  CQuarto  e  CCozinha  associadas. Cada uma destas, por sua vez, envia a 
mensagem acende() para a instância de CLuz correspondente. 
2. A instância  CPortao, ao receber a mensagem  abre(), envia a mensagem  acendeLuz()  para a 
instância de  CGaragem  correspondente. Esta, por sua vez, envia a mensagem  acende()  para a 
instância de CLuz pertinente.
3. A   sua   solução   deverá   fazer   com   que   as   seis   dependências   diretas,   descritas   acima,   sejam 
organizadas de tal forma que cada uma das classes possa ser empregada isoladamente das demais.

44 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Memento (comportamento)
A implementação da operação conhecida por undo pode se beneficiar do padrão Command. Para esta 
finalidade é necessário guardar o estado de um  objeto  que posteriormente será restaurado. De fato, 
para   cada   mudança   no   estado   será   necessário   guardar   uma   cópia   do   estado   anterior,   antes   da 
efetivação da mudança, para que uma possível reversão seja viabilizada. O padrão Memento é uma 
proposta para guardar o estado de um objeto sem violar o encapsulamento deste objeto.
Em Java, a implementação deste padrão pode ser realizada através da serialização do objeto. Ou seja, 
em   determinado   instante,   recuperar   o   estado   anterior   é   tão   simples   quanto   recuperar   o   objeto 
serializado quando aquele estado foi obtido. 
A GoF apresenta uma outra proposta de implementação onde pelo menos três classes distintas estão 
envolvidas.   Uma   delas   é   a   classe  Memento,   responsável   por   armazenar   o   estado   do   objeto 
Originator cuja operação de undo deverá recuperar.  Ou seja, Originator é a classe cujo estado 
desejamos   restaurar.   Durante   a   execução   de   uma   aplicação   poderão   existir   várias   instâncias   de 
Memento  para   um   dado  Originator.   Estas   instâncias   de  Memento  são   gerenciadas   pelo   objeto 
Caretaker. 

Um objeto  Memento  é passivo no sentido em que não realiza nenhum operação com o estado que 


guarda, mas apenas os guarda. Quem cria uma instância de Memento é o Originator correspondente. 
Desta forma não se tem quebra de encapsulamento, pois uma instância de  Caretaker nunca examina, 
investiga ou realiza operação que altera o estado de um  Memento. O único que conhece detalhes de 
um Memento é o objeto do qual este foi obtido. 
A GoF sugere duas operações para um  Originator, que aqui serão colocadas em uma interface. 
Desta forma, todo objeto que implementa esta interface pode participar de operações como undo. Uma 
das operações cria um instância de Memento, a outra recebe um Memento e restaura o estado do objeto 
conforme as informações armazenadas no Memento recebido como argumento. Memento também pode 
ser implementado como uma interface. Desta forma, para cada classe teremos uma implementação 
própria. Também são esperadas pelo menos duas operações nesta interface. Uma atribui o estado a ser 
guardado   em   um  Memento,   e   a   outra   recupera   este   estado.   O   diagrama   abaixo   ilustra   os 
relacionamentos entre estas classes.

Exercícios
Implemente o padrão Memento para  alguma classe que você considere em constante mudança de 
estado e a operação de undo possa ser relevante para o domínio em questão. Ilustre a implementação 
com a construção de um teste de unidade (faça uso do JUnit).

Flyweight (estrutura)
A graduação é uma experiência inesquecível para os alunos e, com certeza para os professores. Entre 
um trabalho e outro é quase inevitável o surgimento de uma pérola. 
Em um destes foi requisitado que os alunos criassem um programa que recebia como entrada um 
número inteiro maior que zero. O programa deveria criar tantos retângulos quanto o número fornecido 
como entrada. Nenhum retângulo deveria estar isolado, ou seja, pelo menos um dos lados deve possuir 
fronteira com outro retângulo. O programa deveria arbitrariamente decidir o tamanho dos retângulos e 

45 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

cada um deles deveria ser pintado com uma dentre um conjunto de quatro cores. A escolha deveria ser 
simples: a cor escolhida para pintar o retângulo não poderia ser nenhuma das cores empregadas pelos 
retângulos vizinhos. 
Há um teorema que diz que todo e qualquer mapa planar pode ser pintado com apenas quatro cores 
sem que vizinhos possuam a mesma cor. O objetivo do programa era mostrar uma evidência, apenas 
uma evidência, de que o teorema está correto, ao contrário do que nossa intuição pode sugerir. 
Uma solução comum foi criar tantas instâncias de retângulo quanto o número fornecido como entrada. 
Para cada instância dessa era criada uma outra para a cor a ser empregada no momento de pintar o 
retângulo e uma instância de Ponto para indicar a posição em que o retângulo deveria ser desenhado. 
A instância ainda possuía duas variáveis para indicar os lados do retângulo. A classe se parecia com 
aquela abaixo. Apenas o construtor e os atributos são fornecidos por simplicidade.

public class Retangulo {


private Cor cor;
private Ponto ponto;
private float base;
private float altura;

public Retangulo(Cor cor, Ponto p, float base, float altura) {


this.cor = new Cor(cor);
ponto = new Ponto(p);
this.base = base;
this.altura = altura;
}
}

Resultado.   Para   números   suficientemente   grandes   os   alunos   descobriram   que   a   memória   do 
computador é finita (“out of memory”). Não era claro, para muitos deles, que uma outra estratégia 
diferente poderia ser empregada. Tal estratégia é o padrão Flyweight. 
Observe   que   temos   apenas   quatro   cores,   qualquer   que   seja   a   quantidade   de   retângulos.   São 
necessárias,   portanto,   apenas   quatro   instâncias   de  Cor  e   não   tantas   quantas   forem   o   número   de 
retângulos. Em conseqüência, a classe acima pode ser reescrita sem que uma nova instância de  Cor
seja criada para cada retângulo, mas que uma das quatro instâncias disponíveis seja reutilizada. Esta 
estratégia emprega o padrão Flyweigth aplicado à classe Cor. 
public class Retangulo {
private Cor cor;
private Ponto ponto;
private float base;
private float altura;

public Retangulo(Cor cor, Ponto p, float base, float altura) {


this.cor = cor;
ponto = p;
this.base = base;
this.altura = altura;
}
}

Observe que agora não mais será criada uma nova instância de Cor e uma nova instância de Ponto, ao 
contrário do exemplo anterior. Isto não impede, contudo, que tenhamos uma instância destas classes 
para cada instância de Retangulo. De fato, teremos que ter uma nova instância de Ponto, mas não 
necessariamente teremos que ter uma nova instância de Cor, conforme comentado anteriormente. 
Para evitar a criação de uma nova instância de   Cor  será necessário a criação de um Factory. Toda 
reuisição a este Factory irá verificar se uma instância de  Cor  já existe. Caso afirmativo, então esta 
instância   será   retornada.   Caso   contrário,   a   instância   é   criada   e   guardada   para   possível   referência 
posterior. Primeiro observe a classe Cor, fornecida abaixo, onde apenas o construtor, por simplicidade 
e os atributos são exibidos.

46 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

public class Cor {


private byte vermelho;
private byte verde;
private byte azul;

public Cor(byte vermelho, byte verde, byte azul) {


this.vermelho = vermelho;
this.verde = verde;
this.azul = azul;
}
}

O emprego do padrão Factory para a classe acima é relativamente simples. Assumindo, como neste 
caso, um número reduzido de possibilidades, um Set pode ser empregado. Primeiro tenta­se recuperar 
o objeto, caso não esteja disponível, então este é inserido. Observe que, neste caso, será necessário 
implementar os métodos equals e hashcode para a classe Cor.
Outro   exemplo.   Em   um   sistema   onde   a   classe   Icone  representa   um   ícone   a   ser   pintado   em 
determinada posição da tela e sabemos que o número de ícones disponíveis é relativamente pequeno, 
independente   do   número   de   vezes   em   que   instâncias   desta   classe   deverão   ser   criadas,   podemos 
empregar o padrão Flyweigth. 
Neste caso, a classe  Icone  possui dados extrínsecos: a posição em que o ícone deverá ser pintado. 
Esta informação, portanto, não pode ser compartilhada e deverá ser armazenada pelo cliente pois, com 
certeza, toda vez que um ícone for desenhado será necessário fornecer esta informação que não será 
por instâncias desta classe. Dado que o número de ícones é relativamente pequeno, podemos criar 
uma instância para cada um deles e, a partir deste ponto, compartilhar estas instâncias criadas tantas 
quantas forem as vezes em isto se fizer necessário. Em uma abordagem que não emprega este padrão, 
toda vez que um ícone fosse empregado teríamos que criar uma nova instância. 

Exercícios
Implemente o padrão Flyweight para o exemplo da classe Cor, comentado acima.

Observer (comportamento)
O padrão Observer tem pelo menos duas outras denominações: Publish/Subscribe e DEM (Delegation 
Event Model). O objetivo é estabelecer um relacionamento entre objetos de tal forma que a ocorrência 
de um evento em um objeto, por exemplo a alteração do estado deste, possa repercutir em objetos 
interessados neste evento. Neste relacionamento há objetos que geram eventos (publishers) e aqueles 
que estão atentos à ocorrência destes eventos (subscribers).
Em uma aplicação onde a interface apresenta várias visões de uma mesma informação, por exemplo, 
cada objeto responsável por uma visão registra (subscribe) o seu interesse com o objeto que contém a 
informação que pode ser alterada (publisher). Uma vez realizado o registro, toda vez que alguma 
operação  altera  o  valor da informação,  todos os objetos interessados são  avisados, o   que oferece 
oportunidade para que estes possam refletir o novo estado da informação. 
Neste caso, o padrão Observer implementa o relacionamento entre um objeto que detém a informação 
e aqueles interessados no estado desta informação, sem que o primeiro esteja acoplado aos últimos. 
Para   ilustrar,   imagine   uma   aplicação   que   continuamente   exibe   o   valor   da   temperatura   corrente, 
registrada em uma instância da classe Temperatura. Toda vez que o valor é alterado, possivelmente 
através da leitura constante de um sensor, a alteração deve ser exibida para o usuário.
A   exibição   pode  ser   realizada   pela   classe  ExibeTemperatura.   Podem   existir  outros  objetos,   por 
exemplo, uma instância da classe ExibeAlertaTemperatura, responsável por emitir aviso sonoro e 
visual que chame a atenção do operador para um valor crítido da temperatura. Naturalmente, todas 
estas questões são relevantes mas não deveriam ser implementadas na classe Temperatura, pois esta 

47 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

classe   é   parte   da   semântica   da   aplicação,   enquanto   as   demais   fazem   parte   da   apresentação,   têm 
propósitos distintos e, portanto, as tarefas que elas realizam devem estar isoladas.
Um outro objeto pode ser responsável pela desativação automática da máquina cuja temperatura está 
elevada.   Neste   caso,   tal   objeto   não   é   da   camada   de   apresentação,   mas   um   objeto   da   camada   de 
negócio interessado em evento em outro objeto desta mesma camada. O padrão Observer oferece uma 
solução elegante para o relacionamento entre estes objetos sem que a instância de Temperatura esteja 
acoplada a qualquer um dos demais objetos deste exemplo. 
A   idéia   do   padrão   Observer   não   é   complexa.   Todo   objeto   que   se   interessa   pela   ocorrência   de 
determinado evento é um observador interessado em algo observável. A biblioteca Java possui uma 
interface   e   uma   classe   para   representar   estes   papéis:  java.util.Observer  e 
java.util.Observable.  A   biblioteca  Java  apenas facilita  a implementação   deste  padrão   através 
deste tipos, contudo, não é necessário empregá­los para se ter acesso aos benefícios deste padrão e, de 
fato, há quem sugere que estes sejam evitados. Esta opção é empregada aqui por simplicidade.
O valor da temperatura pode ser exibido graficamente, através de um termômetro com a marca da 
temperatura   corrente,   assim   como   através   de   uma   pequena   luz,   vermelha   quando   a   temperatura 
ultrapassa  determinado   valor  e  verde  em  caso   contrário.   Outras  visões  do  termômetro   podem  ser 
empregadas. Por simplicidade, contudo, empregaremos outras duas, ambas textuais. A primeira delas 
é fornecida abaixo. Uma instância desta classe simplesmente exibe o valor da temperatura corrente na 
saída padrão.Um comportamento trivial para uma classe da camada de apresentação, mas didática. 
Conforme ilustrado, a interface Observer exige a implementação da operação update.
public class ExibeTemperatura implements Observer {
public void update(Observable observable, Object obj) {
Temperatura t = (Temperatura) observable;
System.out.println(“Temperatura corrente: “ + t.getTemperatura());
}
}

A outra classe é fornecida abaixo. A implementação, neste caso, imprime uma mensagem conforme o 
valor da temperatura.
public class ExibeAlertaTemperatura implements Evento {
public void mudanca(Temperatura t) {
Temperatura t = (Temperatura) observable;
if (!t.isNormal()) {
System.out.println("[ALERTA] Temperatura elevada: " + t.getTemperatura());
}
}
}

As duas classes acima podem, conforme dito anteriormente, exibir tais informações de forma gráfica. 
O texto é uma simplificação para ilustrar o padrão Observer. A classe  ExibeAlertaTemperatura faz 
uso de método que requisita do objeto do domínio informação quanto à normalidade do valor da 
temperatura. Afinal, não é a camada de apresentação que sabe quando uma temperatura está elevada 
ou não.
Ainda nos resta uma aplicação de teste e a classe Temperatura. A classe Temperatura tem o método 
setTemperatura(double) através do qual sensores podem indicar o valor da temperatura corrente. 
Quando este método é chamado, conforme código abaixo, duas mensagens são envidas. Uma indica 
que o valor foi alterado e a outra oferece a possibilidade para que cada Observer possa reagir em 
relação à mudança. É notório que a classe Temperatura não conhece nenhuma classe que depende do 
valor por ela gerido. 
public class Temperatura extends Observable {
private double temperatura;

48 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

public void setTemperatura(double novoValor) {


temperatura = novoValor;
setChanged(); // Indica que houve mudança
notifyObservers(); // Notifica todos os observadores
}

public float getTemperatura() {


return temperatura;
}

public boolean isNormal() {


return (temperatura > 120.0) ? false : true;
}
}

Deve ficar claro que qualquer objeto que implemente Observer poderá registrar o seu interesse em 
alterações   no   estado   de   uma   instância   de  Temperatura.   Ou   seja,   outras   classes   além   daquelas 
fornecidas podem ser criadas, sem que esta criação ou a alteração naquelas fornecidas afete a classe 
Temperatura.   Esta   é   a   independência   que   tanto   se   falou   nos   parágrafos   anteriores   e   permite   a 
independência entre a camada de apresentação e a camada de negócio. Ainda nos falta uma classe de 
teste e outra que faça a simulação de um sensor de temperatura. A de teste é fornecida abaixo. Dois 
observadores são associados a uma instância de Temperatura (objeto observado). Logo em seguida a 
instância de Sensor é associada ao objeto criado de  Temperatura. Por fim, indicamos que deverá ser 
feita uma leitura do sensor a cada segundo. 
public class TestaObserver {
public static void main(String[] args) {
ExibeTemperatura exige = new ExibeTemperatura();
ExibeAlertaTemperatura alerta = new ExibeAlertaTemperatura();

Temperatura t = new Temperatura();

t.adicionaSubscriber(exibe);
t.adicionaSubscriber(alerta);

Sensor sensor = new Sensor(t);


new Timer().schedule(sensor, new Date(), 1000); // Lê sensor a cada segundo
}
}

O código acima depende da classe Sensor. No nosso exemplo didático não é necessário empregar um 
sensor real. Contudo, podemos simulá­lo conforme abaixo. Observe que a cada leitura que fazemos do 
sensor, através do método  run(), um novo valor para a temperatura é aleatoriamente estabelecido 
para a instância de Temperatura.

public class Sensor extends TimerTask {


private Temperatura temp;

public Sensor(Temperatura t) {
temp = t;
}

public void run() {


temp.setTemperatura(Math.random() * 100);
}
}

Nenhuma dependência é estabelecida da classe  Sensor  para as classes de apresentação comentadas 


anteriormente. A classe  Sensor  é altamente coesa e somente tem conhecimento de quem de fato 
precisa ser conhecido por esta classe.

49 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

State (comportamento)
O padrão State é empregado para implementar diagramas de transição de estados. Em tais diagramas 
existem estados e transições entre eles. Eventos provocam transições que, por sua vez, disparam a 
execução de ações. Tanto as transições quanto as ações dependem do estado ativo no momento em 
que o evento em questão é gerado.   Está além do escopo deste texto apresentar detalhadamente um 
diagrama de transição de estados e sua extensão utilizada pela UML (os Statecharts). 
São   muitos   os   cenários   onde   uma   diagrama   de   transição   de   estados   pode   ser   adequadamente 
empregado para modelá­lo. Em um editor gráfico, por exemplo, se o usuário realiza um clique sobre 
um objeto, então este objeto sob a posição do mouse é selecionado. Contudo, se o editor se encontrar 
no modo ou estado de “Criação de figura”, onde a opção círculo encontra­se selecionada, então um 
círculo com centro na posição onde se encontra o mouse será desenhado, ao contrário de uma seleção 
(estado anterior). O padrão State visa facilitar a implementação de cenários como este, ou seja, a 
implementação de código que representa comportamentos distintos conforme o estado corrente. Em 
tempo,   observe   que   no   exemplo   acima   uma   mesma   ação   do   usuário   foi   interpretada   diferente, 
conforme o estado no momento em que o evento foi gerado.
A implementação sugerida deste padrão faz uso de uma interface que deverá definir todos as reações 
possíveis para todos os estados a serem contemplados. Seja Estado esta interface. Cada reação pode 
ser  definida  na  interface  através de  operações  que  representam  os  eventos  possíveis.  Para  todo   e 
qualquer possível evento haverá uma operação correspondente nesta interface. Será a execução destas 
operações que irão provocar mudanças do estado corrente. Cada estado possível do diagrama será 
representado   por   uma   classe   que   implementa   a   interface  Estado  e,   naturalmente,   possui   uma 
implementação para cada operação (evento). É cada implementação específica que identificará como é 
que o estado em questão reage ao evento gerado. 
Na figura abaixo a classe  Porta  representa toda e qualquer porta que pode estar em um de dois 
estados: aberta ou fechada. Cada um destes estados é representado por uma classe que implementa a 
interface Estado. Respectivamente: Aberto e Fechado. É uma instância de Porta que recebe eventos 
(mensagens) como abrir ou fechar. Neste exemplo, uma porta pode receber estes dois únicos eventos, 
conforme modelado abaixo.

A reação de uma instância de Porta a um evento (mensagem abrir() ou fechar()) é simples. Se o 
estado corrente da porta é uma instância de Fechado, ao receber o evento fechar() não haverá o que 
fazer (este comportamento estará implementado no método fechar() da classe Fechado). De forma 
similar, se o estado corrente de uma instância de Porta for uma instância de Aberto e o evento gerado 
é abrir(), então não há o que fazer (à semelhança do caso anterior, a implementação do método em 
questão, abrir(), será nula). Contudo, uma porta aberta pode receber o evento fechar e, neste caso, há 

50 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

uma reação prevista e uma mudança de estado, pois após este evento o estado da porta passa de aberto 
para fechado. Antes de observar como implementar este comportamento, o diagrama abaixo ilustra o 
comportamento descrito por meio de um diagrama de transição de estados, mais especificamente, um 
Statechart (variante de tais diagramas empregada pela UML). 

Dito de outra forma, antes de apresentar detalhes de implementação, um cenário onde se tem uma 
porta aberta será representado por uma instância de Porta que referencia uma instância de classe que 
implementa   a   interface  Estado  que,   neste   caso,   será   uma   instância   de  Aberto.   Eventos   foram 
modelados como mensagens, ou seja, esta porta aberta pode receber o evento fechar ou, conforme 
modelado, a mensagem fechar() e, neste caso, o estado corrente passará a ser fechado. Ou seja, a 
referência que  Porta  retém para  Estado  passará de uma referência para uma instância de  Aberto
para uma instância  de  Fechado.  Por  fim,  o  estado  “Aberto”, ilustrado  acima,  não  apresenta uma 
transição rotulada com o evento abrir, o que significa que este evento, se gerado neste estado, não terá 
nenhum efeito. De forma análoga, o estado “Fechado” não possui nenhuma transição rotulada com o 
evento  “fechar”,   o   que   torna   a  ocorrência  deste  evento   quando   este   estado   estiver   ativo   um  fato 
irrelevante, sem efeito. 
No diagrama de transição de estados acima, o estado inicial é “Fechado”. No modelo este estado é 
representado   por   uma   instância   da   classe  Fechado.   Este   estado   pode   ser   alterado   para   “Aberto” 
(representado por instância da classe  Aberto) com a ocorrência do evento “abrir” (modelado como 
método abrir() definido na interface Estado). Ao representar o cenário da uma porta que pode estar 
aberta ou não e que pode ser aberta ou fechada, conforme feito acima, está­se empregando o padrão 
State. Alguns detalhes de implementação são comentados a seguir. Em particular, como identificar o 
estado corrente? Como ocorre a transição de um estado para outro?
Observe que a classe Porta possui a referência corrente para o estado corrente. Para identificar este 
estado, existem várias possibilidades, desde empregar o operador instanceof de Java para identificar 
qual o objeto que implementa a interface que está sendo referenciado ou, mais simplesmente, por 
meio da mensagem getNome(), que deve ser implementada por toda implementação de Estado. Isto 
apenas responda a primeira pergunta acima. A mais relevante é como se alterna de um estado corrente 
para outro?
Toda implementação de Estado pode receber todo e qualquer evento que pode ser gerado para uma 
porta. No caso da classe  Fechado  a implementação de  fechar()  é trivial, pois não há o que fazer 
quando uma porta se encontra fechada e o evento “fechar” é gerado. Ou seja, a implementação é 
fornecida abaixo.
public void fechar() {}
Por outro lado, se o evento gerado é “abrir”, então a porta deverá ser aberta ou, conforme modelado, o 
estado   corrente   deve   passar   de   uma   instância   de  Fechado  para   uma   instância   de  Aberto. 
Naturalmente, este conhecimento deverá estar implementado no método abrir() da classe Fechado. 
A implementação correspondente é ilustrada abaixo e facilita a compreensão. 
public void abrir() {
setEstado(ABERTO);
}
Acima foi empregada a constante ABERTO para representar a instância correspondente ao estado 
“Aberto”, ou seja, uma instância da classe Aberto. A implementação também foi trivial. Observe que 

51 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

não há ação a ser executada quando se sai do estado “Fechado” nem ação correspondente quando se 
percorre a transição e o estado “Aberto” é ativado. Ou seja, apenas a atualização do estado corrente 
precisa ser efetuada. A implementação de setEstado(Estado) também é simples, conforme abaixo, ao 
fazer uso de uma variável compartilhada por todas as instâncias que implementam Estado.
public void setEstado(Estado corrente) {
EstadoCorrente.corrente = corrente);
}
Há   várias   possibilidades   de   implementação   não   comentadas.   O   objetivo   foi   ilustrar   e   permitir   a 
compreensão   do   padrão   State,   que   pode   ser   empregado   em   vários   outros   contextos.   Em   geral, 
qualquer que seja o objeto cujo comportamento parece ser alterado conforme o estado em que se 
encontra, é um candidato a ser implementado com apoio do padrão State.

Exercícios
1. Implemente o padrão State para o exemplo da classe Porta comentado acima.
2. Faça uso do padrão State para implementar o comportamento registrado no diagrama abaixo. 
Este   comportamento   correspondente   ao   comportamento   esperado   para   toda   instância   da 
classe  Processo. Crie a classe Processo e implemente o comportamento de tal forma que, 
dada  a   ocorrência  de  um   evento,  possivelmente  ocorre  a  transição   para   um  dado   estado, 
juntamente com a execução das ações julgadas oportunas. Neste caso, apenas uma ação deve 
ser executada como resultado da saída do estado “Em avaliação”. Implemente a ação “avisar 
interessado” por meio de uma simples mensagem produzida na saída padrão. Um cenário real 
poderia exigir o envio de correspondência eletrônica para o email do interessado, o que não é 
exigido neste exercício. Por fim, observe que o estado “Avaliado” é uma composição dos 
estados “Indeferido” e “Deferido”. São estes dois últimos que deverão ser tratados. Ou seja, 
“Avaliado”   é   apenas   uma   abstração   que   não   precisa   ser   tratada   da   perspectiva   de 
implementação. Observe que, transcorrido o prazo legal após avaliação de um processo, este 
é conduzido ao estado “Fechado”, independente se o estado é “Indeferido” ou “Deferido”.

3. Implemente o exercício anterior empregando a classe abstrata Estado, em vez da interface 
Estado.
4. Implemente   o   exercício   2   empregando   o   padrão   State   onde   a   interface   Estado  possui   a 
operação  getContexto(Contexto)  onde  Contexto  é   interface   implementada   por   todo 
objeto cujo comportamento está sendo modelado por diagrama de transição de estados e cuja 

52 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

implementação faz uso do padrão State. Neste caso, tal operação será empregada por uma 
instância   que   é   um   Estado   para   alterar   o   estado   corrente   referenciado   pelo   objeto   cujo 
comportamento está sendo implementado por este padrão. Para isto, A interface Contexto 
ainda deverá disponibilizar a operação setEstadoCorrente(Estado). 

Strategy (comportamento)
Implementar algoritmos onde cada um deles pode ser substituído por outro, independente do cliente 
que   o  utiliza,   é   uma   tarefa   para   o   padrão   Strategy.   Um   algoritmo   pode   ser   útil   em   determinado 
contexto e inadequado em outro. Em cenários como este, a coexistência de algoritmos é desejável, 
sem que a escolha de um deles, mais apropriado ao contexto em questão, provoque mudanças nos 
clientes. 
A   ordenação,   por   exemplo,   é   apoiada   por   toda   uma   coleção   de   algoritmos.   Alguns   algoritmos 
consomem   mais   memória   que   outros   e,   em   um   cenário   onde   este   recurso   é   escasso,   a   melhor 
alternativa e fazer uso de um algoritmo parcimonioso.
Em um ambiente comercial podem existir várias regras, que se alteram ao longo do tempo, com o 
propósito de premiar o desempenho de vendedores. Em determinado momento, por exemplo, a maior 
quantidade de vendas por mês no último ano é a regra. Um novo gerente provavelmente irá substituí­
la por outra. Mais uma vez o padrão Strategy pode ser utilizado para proteger o restante do sistema do 
algoritmo adotado.
Mais um cenário de emprego. Em sistemas acadêmicos, uma estratégia pode enfatizar a presença do 
estudante em sala de aula no seu desempenho, outra pode fazer uso exclusivo dos resultados das 
avaliações, enquanto outro, mais holístico, pode considerar vários fatores como o uso da biblioteca, 
desempenho em atividades extracurriculares e outras. Talvez seja interessante a convivência pacífica 
de todas estas estratégias, empregadas em momentos distintos, ou mesmo simultaneamente. 
Quando   este   padrão   não   é   empregado   em   cenários   como   aqueles   ilustrados   acima,   é   comum   a 
presença de estruturas de decisão complexas onde as condições estabelecem quando uma estratégia 
deve ou não ser empregada. Em conseqüência, este padrão é empregado quando se deseja eliminar 
uma estrutura de decisão que controla várias variantes.

Dinâmica
O padrão Strategy emprega três colaboradores: uma interface que encapsula os algoritmos; classes que 
implementam a interface, ou seja, a implementação dos algoritmos e uma classe cliente, que faz uso 
de um algoritmo. O cliente emprega uma instância de uma das classes que implementa a interface. 
Este emprego é realizado sem que o cliente saiba exatamente quem está oferecendo o serviço.
Supondo a existência de um único algoritmo, seja este de ordenação de inteiros fornecidos em um 
array, podemos definir a seguinte interface:
public interface SortStrategy {
void sort(int[] int);
}

Existem   vários   algoritmos   de   ordenação.   Todo   aquele   que   o   cliente   poderá   fazer   uso   deverá   ser 
encapsulado   em   uma   classe   que   implementa   a   interface   SortStrategy.   Um   famoso   método   de 
ordenação   conhecido   por   Quicksort,   por   exemplo,   pode   ser   implementado   conforme   abaixo.   A 
implementação do algoritmo foi omitida, mas pode ser encontrada facilmente na literatura.
public class QuickSort implements SortStrategy {
void sort(int[] ints) {
// Implementação do QuickSort
}
}

53 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Se houver interesse em outro algoritmo de ordenação, por exemplo a ordenação pelo método da bolha, 
então o algoritmo correspondente deverá ser encapsulado em uma classe como aquela abaixo.
public class BubbleSort implements SortStrategy {
void sort(int[] ints) {
// Implementação do BubbleSort
}
}

Neste ponto, todos os algoritmos de interesse estão devidamente encapsulados em suas respectivas 
classes. Cada uma destas implementa a interface SortStrategy. 
O cliente, conforme ilustrado abaixo, faz uso do serviço de ordenação, ou seja, de um objeto de uma 
classe que implementa SortStrategy. Este cliente, em particular, precisa do serviço de ordenação, 
independente do algoritmo que a implementa. É fácil observar que a classe  Cliente  desconhece a 
existência das classes QuickSort e BubbleSort. Ou seja, é independente destas implementações. O 
serviço é usufruído através de uma referência para SortStrategy.  Ao ser criada uma instância de 
Cliente, uma instância de uma classe que implementa SortStrategy deverá ser fornecida. 

public class Cliente {


private SortStrategy ss;
private int[] umArrayInt = new int[100];

public Cliente(SortStrategy ss) {


this.ss = ss;
}

public void fazAlgumaCoisa() {


...
ss.sort(umArrayInt);
...
}
}

O   exemplo   acima   elucida   os   principais   elementos   que   compõem   o   emprego   do   padrão   Strategy. 
Contudo, você terá que fornecer mais código para poder executá­lo. Aquele abaxo, ao contrário, está 
completo, além de também servir como exemplo. Por simplicidade, todo o código é fornecido em um 
único arquivo. Uma pequena explanação é fornecida logo após o código. 
interface ImprimeArray {
void imprime(int[] a);
}

class OrdemNatural implements ImprimeArray {


void imprime(int[] a) {
for (int i = 0; i < a.length; i++) {
System.out.print(“ “ + a[i]);
}
}
}

class OrdemInversa implements ImprimeArray {


void imprime(int[] a) {
for (int i = a.length – 1; i >= 0; i--) {
System.out.print(“ “ + a[i]);
}
}
}

class Cliente {
private ImprimeArray srvImpressao;
private int[] ints = new int[] { 1, 2, 3, 4, 5 };

public Client(ImprimeAray srv) {


srvImpressao = srv;
}

public void exibeEstado() {

54 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

srvImpressao.imprime(ints);
}
}

public class TestaStrategy {


public static void main(String[] args) {
ImprimeArray estrategiaA = new OrdemNatural();
ImprimeArray estrategiaB = new OrdemInversa();

Cliente c1 = new Cliente(estrategiaA);


Cliente c2 = new Cliente(estrategiaB);

c1.exibeEstado(); // Imprime 1 2 3 4 5
c2.exibeEstado(); // Imprime 5 4 3 2 1
}
}

Toda  implementação  do Strategy  envolve  algoritmos, aqui  encapsulados  nas classes   OrdemNatural  e 


OrdemInversa. Ambas implementam a interface ImprimeArray. Os algoritmos imprimem o conteúdo de 
um  array de inteiros. Um deles na ordem crescente do índice e outro na ordem inversa. Uma classe para 
testar o emprego do padrão cria instâncias das duas implementações e as repassa para duas instâncias da 
classe  Cliente,   que   depende   do   serviço   de   impressão   de   inteiros.   Sem   conhecer   qual   algoritmo   é 
utilizado, os objetos da classe Cliente requisitam a impressão. Neste exemplo a classe  TestaStrategy
define   qual   algoritmo   empregar.   Uma   aplicação   real   pode   usar   uma   Factory   (3)   para   decidir   qual 
implementação usar, combinado com um Singleton (14) para assegurar a existência de uma única instância. 

Exercício
A   seqüência   de   passos   abaixo   ilustra   o   emprego   do   padrão   Strategy   para   eliminar,   ou   melhor, 
substituir   uma   estrutura   de   decisão   complexa.   O   cenário   é   uma   empresa   que   precisa   calcular   o 
desconto que oferece aos seus clientes em cada venda. O desconto é baseado no histórico de vendas 
do cliente.
1. Diz­se que dois objetos wrapper do tipo Short que representam o mesmo valor primitivo na 
faixa de ­128 e 127 são iguais, conforme o operador  ==, se obtidos por meio do método 
estático  valueOf(short)  ou  valueOf(String).   Contudo,   se   duas   instâncias   de  Short
forem obtidas a partir do emprego do operador new, então o resultado são dois novos objetos. 
O   mesmo   é   dito   para   objetos   do   tipo  Integer.   Ou   seja,   a   comparação   empregando  ==
retornará true para duas instâncias de Integer que representam o valor 127, por exemplo, 
quando obtidas por meio de Integer.valueOf(127), enquanto esta mesma comparação irá 
retornar o valor false caso o valor primitivo representado seja 128. Esta afirmação também é 
válida   para   o   tipo  Byte.   Escreva   um   programa   em   Java   que   teste   a   veracidade   destas 
afirmações para os tipos Byte, Short e Integer. O padrão Strategy deverá ser empregado.
2. Crie a interface Java Tarefa contendo uma única operação: int executa(). Crie a interface 
Java  Concorrente  que   estende   a   interface  Tarefa.   Contudo,   nenhuma   operação   será 
adicionada. Neste caso, qualquer implementação da interface Concorrente deverá executar a 
tarefa implementada pelo método  int executa()  de forma concorrente. Crie a interface 
Java Sequencial que também estende a interface Tarefa sem acrescentar operações. Uma 
implementação   desta   interface,   ao   contrário   de   uma   implementação   da   interface   anterior, 
deverá executar a tarefa correspondente de forma seqüêncial, ou  seja, sem o  emprego de 
concorrência (threads). Escreva a classe  Tempo  cujo construtor recebe uma referência para 
Tarefa  como   argumento.   Esta   classe   deve   implementar   a   interface   Tarefa  e,   quando   o 
método executa() for chamado, deverá executar a tarefa implementada pelo objeto recebido 
pelo construtor e computar o tempo gasto para a execução da tarefa correspondente. A classe 
Tempo não possuirá outra dependência senão para a interface Tarefa. Crie um programa em 
Java que use a classe  Tempo  para verificar que, conforme a documentação da API de Java 
informa,   a   classe  StringBuilder  é   “mais   rápida”   que   a   classe  StringBuffer,   embora 
ambas ofereçam as mesmas funcionalidades. Crie um segundo programa em Java que faça 

55 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

uso preferencial das interfaces fornecidas acima, se for o caso, para verificar que, de fato, a 
classe StringBuilder não é segura se empregada por vários threads, ao contrário da classe 
StringBuffer que, ao contrário da anterior, é dita thread safe.

3. Uma empresa que comercializa vários produtos possui um esquema de desconto conforme 
algumas regras. Regras de desconto para compras: (a) se o cliente é “antigo” (mais de dois 
anos) o desconto é de 2% + 0,12% por cada ano de clientela além de dois anos (qualquer que 
seja o produto adquirido) até um desconto adicional máximo de 1%, o que perfaz um total de 
no máximo 3%; (b) se o cliente é “antigo” e “freqüente” (comprou nos últimos três meses) 
então o desconto é de 3% para os produtos A e B adquiridos em quantidade superior a 20 
unidades; (c) se o cliente compra acima de 5% do estoque disponível, então o desconto é de 
2% para o produto e de 1,5% para os demais produtos. O que se observa neste pequeno 
número de regras é que as condições são relativamente simples, assim como as expressões de 
cálculo   correspondentes.   Outros   cenários,   contudo,   podem   apresentar   bem   mais 
complexidade. Neste exercício você deverá implementar tais regras com o padrão Strategy. A 
interface  DescontoStrategy  deve   ser   criada.   Esta   interface   inclui   a   operação   double
desconto(). Uma implementação desta interface será criada para cada regra de desconto. 
Por exemplo, para a primeira regra pode ser criada a classe ClienteAntigo.  Naturalmente, a 
implementação de  desconto()  irá retornar a soma de 2% e 0,12% para cada ano além de 
dois anos de clientela até o limite de 1%, conforme a regra supracitada. Para este exercício 
serão necessárias três implementações de DescontoStrategy. A classe NotaFiscal poderá 
ter acesso à implementação a ser utilizada por meio do construtor e, neste caso, fica sob a 
responsabilidade do cliente desta classe identificar qual a regra ou conjunto de regras a ser 
empregado. Uma alternativa é empregar o padrão Factory para decidir qual a implementação 
a ser empregada conforme a compra e o cliente. Em qualquer um destes casos, é necessário 
conhecer todos os itens da nota para se obter o desconto final, pois várias regras podem ser 
aplicáveis em cenários distintos, assim como informações sobre o cliente. Independente do 
caso, a NotaFiscal simplesmente delegará para o conjunto de estratégias correspondente o 
cálculo do desconto. Convém ressaltar que, nesta proposta, não é a instância de NotaFiscal
a responsável pela identificação do desconto. Esta responsabilidade foi transferida para um 
conjunto de implementações de  DescontoStrategy. Defina o projeto UML correspondente 
à   implementação   requisitada   deste   padrão.   Onde   detalhes   forem   necessários   mas   não 
fornecidos pelo exercício, complemente­o da forma que considerar apropriado.

Visitor (comportamento)
Quando se deseja realizar uma operação em um conjunto de objetos que possuem interfaces distintas, 
ou   seja,   as   classes   correspondentes   são   de   hierarquias   distintas,   não   é   possível   fazer   uso   de 
polimorfismo. 
Mesmo que polimorfismo seja uma opção, acrescentar métodos de naturezas distintas em uma mesma 
classe reduz a coesão desta classe. Por exemplo, no exercício pertinente ao padrão Composite (26) foi 
sugerida a criação da classe Elemento, ancestral tanto de  Arquivo  quanto de  Diretorio. A classe 
Elemento  define a operação  long tamanho(), que retorna a quantidade de bytes do arquivo ou 
diretório correspondente. Se em um momento futuro desejarmos a geração de um relatório contendo 
detalhes da permissão de acesso para cada arquivo e diretório, por exemplo, então estaremos reunindo 
em uma só classe, a contagem do tamanho de um arquivo e questões de permissão. Estas operações 
até que podem coexistir sem significativa perda de coesão, contudo, uma operação que abre cada 
arquivo e identifica qual o formato deste, por exemplo, executável, PDF ou outro, já envolve tarefas 
bem distintas das anteriores. 
Se isto não é suficiente, então pode­se sugerir um infindável lista de tarefas e, seguindo este modelo, 
todas   estariam   em   uma   única   classe.   O   padrão   Visitor   é  uma   forma   de   implementar   os   serviços 

56 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

necessários em classes distintas, isoladas daquelas que compõem a estrutura de objetos que desejamos 
trabalhar.
Também convém ressaltar que às vezes é necessário realizar operações distintas que nem mesmo 
foram previstas no momento de definição das classes. Nestes casos, o padrão Visitor também nos 
oferece uma alternativa.
Para   fazer   uso   deste   padrão   convém   identificar   duas   classes   distintas.   Uma   é   a   classe   abstrata 
Visitor. Classes concretas derivadas de  Visitor  são responsáveis por implementar as operações 
descritas acima. Para cada operação deve ser definida uma classe concreta que herda de Visitor. A 
operação é implementada no método  visit(TipoClasse), onde  TipoClasse  é o nome da classe 
cujas   instâncias   serão   visitadas,   ou   seja,   a   operação   está   sendo   definida   para   objetos   do   tipo 
TipoClasse, conforme o código abaixo. 

public abstract class Visitor {


public abstract void visit(ClassName ref);
}

Caso a classe concreta implemente a operação para mais de um tipo de classe, então a única operação 
fornecida acima deverá ser sobrecarregada. Neste caso, deverão existir tantas operações quantas forem 
as classes dos objetos que podem ser visitados. Por exemplo, se a classe Computador e a classe Livro
possuem instâncias que deverão ser processadas por tais operações, então a classe acima deverá ser 
reescrita como abaixo. 

public abstract class Visitor {


public abstract void visit(Computador ref);
public abstract void visit(Livro ref);
}

Dessa forma, toda classe concreta que herda daquela acima poderá realizar uma operação tanto para 
instâncias de Computador quanto para instâncias de Livro.  Para que isto seja possível é necessário 
que as classes Computador e Livro implementem o método abaixo.

public void accept(Visitor v) {


v.visit(this);
}

Dinâmica
Quando desejarmos que uma determinada operação seja realizada para instâncias da classe   X, esta 
operação   deverá   ser   implementada   em   uma   classe   que   herda   de  Visitor  através   do   método 
visit(X).   Digamos   que  Operacao  seja   o   nome   desta   classe.   A   classe  X,   conforme   comentado 
anteriormente, deverá implementar o método accept(Visitor). 
Neste cenário, as instâncias para as quais a operação será realizada deverão receber a mensagem 
accept(op) onde op é uma instância de Visitor. O cliente do padrão Visitor deverá, naturalmente, 
conhecer tanto as instâncias quanto o objeto que implementa a operação. 
Cada   mensagem   enviada   por   este   cliente,   conforme   o   código   do   método   accept(Visitor), 
fornecido   acima,   dará   origem   a  uma   outra,   desta   vez,   da   instância   sobre   a   qual   a  operação   será 
realizada para o objeto que implementa a operação. O objeto que implementa a operação é fornecido 
através do argumento, desta forma, o próprio objeto poderá requisitar ao objeto   op  a execução da 
operação que este implementa. 
Este modelo faz uso do que é conhecido por double­dispatching, pois o cliente poderia simplesmente 
informar ao objeto  que implementa a operação qual o objeto sobre o qual a operação  deverá ser 
realizada.   Ao   contrário,   o   cliente   envia   uma   mensagem   ao   objeto   e  este,   conforme   o   argumento 
fornecido, envia a requisição da operação ao objeto que a implementa. 

57 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

Exercícios
Implemente   o   padrão   Visitor   para   uma   estrutura   de   objetos   onde   arquivos   e   diretório   são 
representados   por   instâncias   de   classes   correspondentes,   assim   como   realizado   para   o   padrão 
Composite, onde o visitante deverá simplesmente imprimir para cada arquivo e/ou diretório, se o 
programa que está realizando o percurso possui permissão de escrita ou não.

Avaliação
1. Na   documentação   da   API   de   Java   lê­se   para   a   classe  java.util.Currency:   “A   classe   é 
projetada de tal forma que não há mais de uma instância de  Currency para qualquer que seja a 
moeda (currency) considerada. Quais os padrões de projeto que mais diretamente estão associados 
a esta afirmação?

2. O padrão Observer é adequadamente empregado em um cenário onde mudanças em um objeto 
repercute em vários outros sem que o objeto tenha conhecimento da existência destes outros.

3. O padrão Observer pode ser implementado de várias formas. Em uma delas o que é observado 
registra seu interesse em avisar os objetos que precisam ser avisados quando da ocorrência de um 
evento significativo. 

4. O padrão Observer pressupõe que o Observer notifica seus Observables sem diretamente conhecê­
los.

5. O   padrão   Observer   pode   ser   representado   parcialmente   usando­se   a   UML   onde   a   classe 
Observable é dependente da interface Observer.

6. O padrão Observer estabelece uma dependência do Observer para o Observable. 

7. A   classe   abaixo   faz   uso   de  deep   copy  para   implementar   o   padrão   Prototype.
public class X { public Object clone() { return new X(); } }

8. O padrão AbstractFactory faz uso obrigatório do padrão Factory.

9. A ferramenta Jmock faz uso significativo do padrão Prototype. 

10. Conforme   a   documentação   de   Java,  DateFormat  é   uma   classe   abstrata.   O   método 


getDateInstance() é abstrato e retorna uma instância de DateFormat. Este é um exemplo do 
padrão Factory (Factory Method). 

11. Em uma instituição que instala aparelhos de som em automóveis existem várias configurações 
predefinidas para escolha do cliente: mínima, simples, luxo, super e extravagante. Para cada uma 
destas configurações tem­se CaixaSom (representa a marca, quantidade e detalhes das caixas de 
som da configuração), AparelhoSom (representa a marca e o modelo do aparelho de som) e assim 
por   diante.   Naturalmente,   a   elaboração   de   cada   configuração   levou   em   consideração   a 
compatibilidade entre estes componentes. Há componente que só funciona com outro componente 
compatível,   ambos   parte   de   uma   dada   configuração.   Qual   o   padrão   de   projeto   que   melhor 
expressa este cenário?

12. O emprego do padrão Decorator é uma alternativa para herança. Enquanto herança acrescenta 
comportamento   em   tempo   de   compilação,   um   Decorator   pode   acrescentar   funcionalidade   em 
tempo de execução. 

58 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

13. Em uma implementação típica de um Decorator o objeto decorado é fornecido como argumento 
ao   construtor   daquele   que   decora.   Assuma   que  InputStream  é   argumento   do   construtor   de 
ObjectInputStream.   Em   conseqüência,   este   seria   um   exemplo   do   emprego   do   padrão 
Decorator.

14. Poder   optar   pela   escolha   de   um   algoritmo   a   ser   empregado   em   determinado   contexto, 
possivelmente em tempo de execução, é um exemplo do emprego do padrão Abstract Factory.

15. Uma interface que possui uma operação, digamos, execute(), implementada por várias classes, 
cada uma delas explorando uma forma distinta de se realizar a mesma operação é um exemplo do 
padrão Strategy.

16. Qual o padrão mais próximo do diagrama incompleto ilustrado abaixo. 

17. Qual   o   padrão   mais   próximo   dos   diagramas   incompletos   ilustrados   abaixo.

18. Quando a estrutura de um algoritmo (ou “esqueleto”) é predefinida, mas cujas partes podem ser 
implementadas de formas específicas para um contexto, tem­se o exemplo do emprego do padrão 
Template. 

19. Um texto típico contém milhares, talvez milhões de caracteres. Representar cada caractere como 
um objeto em Java pode ser uma operação de grande consumo de memória. Isto é ainda mais 
agravante quando se sabe que os caracteres se repetem e, boa parte empregam o mesmo estilo de 
letra. Ou seja, para um texto em uma língua com c caracteres e f fontes diferentes, é necessário 
termos c x f objetos, em vez de milhares ou milhões de caracteres. Naturalmente, para cada um 
dos   c   x   f     objetos   teríamos   informação   específica   como   a   posição   do   caractere,   mas   não 
repetiríamos nem o caractere nem a fonte por ele empregada. Qual o padrão de projeto que pode 
ser empregado neste caso?

59 de 60
Padrões de Projeto: Uma Introdução aos Padrões da “Gangue dos Quatro”                       Fábio Lucena (INF/UFG)

20. Todo programa em Java pode ser escrito em uma única classe e, neste caso, quanto maior esta 
classe pior é o projeto correspondente. Qual o princípio que estaria sendo violado?

Considerações finais
Padrões de projeto são uma ferramenta de projetistas de software. Como toda ferramenta, primeiro é 
preciso   conhecê­la,   dominar  o   uso   e   identificar   o   contexto   adequado   para  empregá­la.   Este  texto 
contribui com estes três objetivos quando a ferramenta é padrão de projeto. Embora o foco tenha se 
restringido  aos  padrões  produzidos pela  “Gangue  dos  Quatro”,  acredita­se que o  leitor que tenha 
apresentado   desenvoltura   na   resolução   dos   exercícios   aqui   fornecidos   não   terá   dificuldades   para 
compreender outros padrões de projeto.  

60 de 60

Você também pode gostar