Logo Passei Direto
Buscar
Material
páginas com resultados encontrados.
páginas com resultados encontrados.
details

Libere esse material sem enrolação!

Craque NetoCraque Neto

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

details

Libere esse material sem enrolação!

Craque NetoCraque Neto

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

details

Libere esse material sem enrolação!

Craque NetoCraque Neto

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

details

Libere esse material sem enrolação!

Craque NetoCraque Neto

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

details

Libere esse material sem enrolação!

Craque NetoCraque Neto

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

details

Libere esse material sem enrolação!

Craque NetoCraque Neto

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

details

Libere esse material sem enrolação!

Craque NetoCraque Neto

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

details

Libere esse material sem enrolação!

Craque NetoCraque Neto

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

details

Libere esse material sem enrolação!

Craque NetoCraque Neto

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

details

Libere esse material sem enrolação!

Craque NetoCraque Neto

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Prévia do material em texto

Padrões de Projetos de Software com Java 
Marcio Quirino - 1 
 
SUMÁRIO 
Caminho do Brilho: Saiba Quais Conteúdos Você Irá Aprender Conosco........................................... 8 
Onboarding................................................................................................................................................. 8 
Padrões GoF de Criação ....................................................................................................................... 9 
Descrição ................................................................................................................................................... 9 
Propósito .................................................................................................................................................... 9 
Preparação ................................................................................................................................................. 9 
Introdução .................................................................................................................................................. 9 
1. Padrão de projeto Factory Method ........................................................................................ 11 
Intenção do padrão Factory Method ......................................................................................................... 11 
Problema do padrão Factory Method ................................................................................................... 11 
Solução do padrão Factory Method ..................................................................................................... 14 
Consequências e padrões relacionados ao Factory Method ................................................................ 16 
2. Padrão de projeto Abstract Factory ....................................................................................... 17 
Intenção do padrão Abstract Factory ....................................................................................................... 17 
Problema do padrão Abstract Factory .................................................................................................. 17 
Solução do padrão Abstract Factory .................................................................................................... 19 
Consequências e padrões relacionados ao Abstract Factory .................................................................. 22 
3. Padrão de projeto Builder ...................................................................................................... 22 
Intenção do padrão Builder ...................................................................................................................... 22 
Problema do padrão Builder ................................................................................................................. 22 
Solução do padrão Builder ................................................................................................................... 23 
Consequências e padrões relacionados ao Builder ............................................................................. 26 
4. Padrões de projeto Prototype e Singleton ............................................................................. 26 
Intenção do padrão Prototype .................................................................................................................. 26 
Problema do padrão Prototype ............................................................................................................. 26 
Solução do padrão Prototype ............................................................................................................... 28 
Consequências e padrões relacionados ao Prototype ......................................................................... 29 
Intenção do padrão Singleton................................................................................................................... 30 
Problema do padrão Singleton ............................................................................................................. 30 
Solução do padrão Singleton ............................................................................................................... 30 
Consequências e padrões relacionados ao Singleton .......................................................................... 31 
5. Conclusão............................................................................................................................... 32 
Considerações Finais ............................................................................................................................... 32 
Referências .............................................................................................................................................. 32 
Explore+ ................................................................................................................................................... 32 
Padrões GoF Estruturais ..................................................................................................................... 34 
Descrição ................................................................................................................................................. 34 
Propósito .................................................................................................................................................. 34 
Preparação ............................................................................................................................................... 34 
Padrões de Projetos de Software com Java 
Marcio Quirino - 2 
 
Introdução ................................................................................................................................................ 34 
1. Padrão de projeto Adapter ..................................................................................................... 35 
Intenção do Padrão Adapter..................................................................................................................... 35 
Problema Resolvido pelo Padrão Adapter............................................................................................ 35 
Solução do Padrão Adapter ................................................................................................................. 37 
Consequências e padrões relacionados ao padrão Adapter ................................................................ 39 
2. Padrões de projeto Bridge e Decorator ................................................................................. 39 
Intenção do padrão Bridge ....................................................................................................................... 39 
Problema resolvido pelo padrão Bridge................................................................................................ 40 
Solução do padrão Bridge ........................................................................................................................ 42 
Consequências e padrões relacionados ao padrão Bridge .................................................................. 43 
Intenção do padrão Decorator .................................................................................................................. 44 
Problema resolvido pelo padrão Decorator .......................................................................................... 44 
Solução do padrão Decorator ............................................................................................................... 45 
Consequências e padrões relacionados ao padrão Decorator ............................................................. 47 
3. Padrões de projeto Composite e Facade .............................................................................. 48 
Intenção do padrãoem PDF 
22 // retornar conteúdo da nota no formato PDF 
23 } 
24 
25 private byte[] gerarNotaXLS(NotaNegociacao nota) { 
26 // construir cabeçalho em XLS 
27 // listar os itens da nota em XLS 
28 // gerar sumário em XLS 
29 // retornar conteúdo da nota no formato XLS 
30 } 
31 
32 } 
 A operação exportarNota recebe a nota de negociação a ser exportada e o formato de exportação 
(XML, PDF ou XLS). 
A solução apresentada não é adequada, pois, além de concentrar em um único módulo todas as 
possíveis representações de exportação da nota de negociação, o algoritmo de construção é repetido em 
cada formato específico. Além disso, o módulo deve ser modificado a cada nova forma de representação 
que for necessária para a nota, violando o princípio Open Closed, um dos princípios SOLID. 
Solução do padrão Builder 
A solução proposta pelo padrão Builder consiste em separar a criação de objetos complexos de quem 
demanda esses objetos, conforme a estrutura definida no diagrama de classes a seguir: 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 24 
 
A interface Builder define as operações que criam as diferentes partes de um produto. Cada forma 
particular de criação desse produto é definida em uma classe ConcreteBuilder, que implementa as 
operações específicas para a criação das partes definidas na interface Builder. 
O participante Director corresponde à classe que constrói o produto utilizando a interface Builder. 
Nessa solução, a classe Director fica isolada do conhecimento sobre as diferentes formas de representação 
do produto a ser construído. 
O diagrama de sequência a seguir ilustra a colaboração entre os participantes do padrão Builder: 
 
Nessa colaboração, o elemento Client representa o objeto que solicita a criação de um produto para 
o Director. Para isso, ele cria o Builder específico para o produto desejado (ConcreteBuilder), injetando-o na 
instanciação da classe Director. 
A partir daí, o objeto Director é responsável por criar as diferentes partes do produto, chamando as 
operações específicas do ConcreteBuilder (buildPart_1, buildPart_2 etc.). 
• O Builder concreto adiciona as partes solicitadas pelo Director, pois somente ele conhece os 
detalhes da representação do produto. 
• Ao final, o elemento Client pede o produto construído para o ConcreteBuilder por meio da 
operação GetResult. 
O diagrama a seguir apresenta a aplicação do padrão Builder no problema apresentado. A classe 
ExportadorNota corresponde ao participante Director na estrutura definida pelo padrão. NotaBuilder 
representa a interface Builder, enquanto NotaPDFBuilder e NotaXLSBuilder correspondem ao participante 
ConcreteBuilder. Cada builder específico constrói uma representação específica do produto (NotaPDF e 
NotaXLS). 
Padrões de Projetos de Software com Java 
Marcio Quirino - 25 
 
 
O código a seguir ilustra a estrutura da implementação da solução, utilizando o padrão Builder. 
A classe ExportadorNota recebe um builder em seu construtor. Esse builder é utilizado no método 
exportarNota para gerar as partes que compõem uma nota exportada tanto em PDF quanto em XLS, isto é, 
o cabeçalho, os itens negociados e o sumário. 
1 public class ExportadorNota { 
2 private NotaBuilder builder; 
3 public ExportadorNotaNegociacao(NotaBuilder builder) { 
4 this.builder = builder; 
5 } 
6 public void exportarNota(NotaNegociacao nota) { 
7 builder.gerarCabecalho(nota); 
8 builder.gerarItensNota(nota); 
9 builder.gerarSumario(nota); 
10 } 
11 } 
A classe ComandoExportarNotaPDF é um exemplo de cliente do exportador de nota. O método 
executar instancia um builder concreto (NotaPDFBuilder), cria um diretor (ExportadorNota), passando o 
builder a ser utilizado, e chama a operação de construção do produto desejado (exportarNota). O último 
passo é solicitar ao builder concreto o objeto NotaPDF construído. 
1 public class ComandoExportarNotaPDF { 
2 public NotaPDF executar(NotaNegociacao nota) { 
3 NotaPDFBuilder builder = new NotaPDFBuilder(); 
4 ExportadorNota diretor = new ExportadorNota (builder); 
5 diretor.exportarNota(nota); 
6 return builder.obterNotaPDF(); 
7 } 
8 } 
Padrões de Projetos de Software com Java 
Marcio Quirino - 26 
 
Comentário 
Note que, nessa solução, o algoritmo geral de exportação é definido apenas na classe ExportadorNota. Além 
disso, a estrutura condicional baseada no formato desejado, presente na solução original, não é mais necessária. 
Dessa forma, novas representações de exportação da nota de negociação podem ser adicionadas ao sistema, 
bastando adicionar novos builders e produtos correspondentes. 
Existem algumas questões importantes na implementação desse padrão. 
• A primeira é se o objeto Builder deve dar acesso apenas ao produto pronto, isto é, após a 
realização de todas as etapas de construção, ou se ele pode dar acesso às partes 
intermediárias já construídas do produto. 
• No exemplo apresentado, como o objeto que desempenha o papel de Director não precisa 
acessar as partes em seu algoritmo de construção, cada Builder concreto fornece acesso 
apenas ao produto construído por meio das operações obterNotaPDF e obterNotaXLS. 
Entretanto, caso necessário, é admissível que as operações de construção (buildPart_1, 
buildPart_2 etc.) retornem uma parte intermediária do produto. 
• Outra questão é se os produtos devem ser estruturados em uma hierarquia. No exemplo, as 
classes NotaPDF e NotaXLS não foram definidas com uma superclasse comum, pois 
assumimos que elas seriam utilizadas de forma bem específica. Entretanto, nada impede que 
elas sejam derivadas de uma superclasse ou implementem uma interface genérica. 
Consequências e padrões relacionados ao Builder 
O padrão Builder é aplicável na construção de objetos complexos e que possam ter diferentes 
representações internas. Encapsulando o conhecimento dessas representações em builders concretos que 
implementam uma interface genérica comum, os clientes ficam isolados da forma como esses objetos são 
internamente construídos. 
Atenção 
O padrão Abstract Factory, assim como o Builder, pode construir objetos complexos. A diferença principal entre 
os dois padrões é que o Builder oferece um mecanismo de construção de um objeto complexo em etapas, enquanto o 
foco do Abstract Factory é definir famílias de produtos. Um produto da família é retornado com apenas uma chamada 
de operação. 
O padrão Composite é utilizado para representar objetos compostos por outros em uma hierarquia 
de especializações de um mesmo elemento comum, como ocorre, por exemplo, em uma estrutura de 
diretórios e arquivos. O padrão Builder pode ser utilizado para implementar a construção de objetos com 
uma estrutura de composição complexa resultante da utilização do padrão Composite. 
4. Padrões de projeto Prototype e Singleton 
Intenção do padrão Prototype 
O padrão Prototype permite a instanciação de objetos a partir da geração de uma cópia de um objeto 
protótipo, fazendo com que o módulo cliente não precise conhecer a classe específica que está sendo 
instanciada. 
Problema do padrão Prototype 
Suponha que, no problema apresentado no padrão Abstract Factory, os decodificadores da 
mensagem Registrar Cliente, ao invés de criarem apenas objetos da classe MsgRegistrarCliente, tivessem 
de criar objetos de classes específicas conforme a origem da mensagem. 
Isso significa que a classe RegistrarClienteXMLDecoder, por exemplo, ao invés de criar uma 
instância de MsgRegistrarCliente, teria de criar uma instância de MsgRegistrarCliente_X, 
Padrões de Projetos de Software com Java 
Marcio Quirino - 27 
 
MsgRegistrarCliente_Y ou MsgRegistrarCliente_Z, dependendo da organização origem da mensagem, 
imaginando que a validação de cada registro de cliente variasse conforme a organização. 
O diagrama a seguir ilustra essa solução. Definimos uma especialização de MsgRegistrarCliente 
para cada origem (veja os sufixos X,Y e Z definidos nas subclasses). Cada subclasse implementa um 
método específico de validação da mensagem. 
 
Você consegue perceber que essa solução adiciona complexidade ao decodificador? 
Veja, no código a seguir, como a classe RegistrarClienteXMLDecoder fica mais complexa, uma vez 
que tem de conhecer cada subclasse de MsgRegistrarCliente: 
1 public class RegistrarClienteXMLDecoder { 
2 public MsgRegistrarCliente decode(String textoMsg, String origem) { 
3 MsgRegistrarCliente msg; 
4 if (“X”.equals(origem)) { 
5 msg = new MsgRegistrarCliente_X(); 
6 else if (“Y”.equals(origem)) { 
7 msg = new MsgRegistrarCliente_Y(); 
8 else if (“Z”.equals(origem)) { 
9 msg = new MsgRegistrarCliente_Z(); 
10 } 
11 // … aqui viria o código de decodificação e preenchimento dos atributos 
12 // do objeto MsgRegistrarCliente 
13 return msg; 
14 } 
15 } 
Padrões de Projetos de Software com Java 
Marcio Quirino - 28 
 
Inserir o processo de decisão sobre o objeto a ser instanciado na implementação do método de 
decodificação da mensagem, além de adicionar complexidade, torna a implementação inflexível à adição de 
novas origens, pois teríamos de modificar o código inserindo novos comandos condicionais, o que é uma 
clara violação do princípio Open Closed, um dos princípios SOLID. 
Solução do padrão Prototype 
O diagrama a seguir ilustra a estrutura da solução proposta pelo padrão Prototype: 
 
Comentário 
A ideia central do padrão é fazer com que uma classe cliente que precise criar instâncias de uma subclasse 
específica ou de diferentes subclasses registre uma instância protótipo dessa(s) subclasse(s) e chame a operação 
clone do protótipo registrado sempre que precisar de uma nova instância. 
A operação clone é definida em cada subclasse e retorna para o módulo cliente uma nova instância 
com uma réplica de seu estado. Dessa forma, o módulo cliente não sabe qual subclasse específica foi 
instanciada, e novas subclasses podem ser adicionadas ao esquema, sem que o cliente precise ser 
modificado. 
Você consegue visualizar como ficaria a solução do problema apresentado com a aplicação da 
estrutura proposta pelo padrão Prototype? 
Em Java, todo objeto já oferece uma implementação padrão para a operação clone, conhecida pelo termo 
shallow copy. Essa implementação padrão apenas copia os valores dos atributos de um objeto para sua réplica. 
Se um objeto Venda, por exemplo, possuir um atributo que seja uma referência para um objeto Cliente 
relacionado, a cópia desse objeto Venda compartilhará com o objeto original a referência para o mesmo objeto Cliente, 
ou seja, em uma shallow copy, os objetos referenciados pelo objeto original não são clonados. 
Se você precisar criar cópias dos objetos referenciados, deverá criar uma implementação específica da 
operação clone, sobrepondo a implementação padrão disponível na classe Object. Esse processo de geração da cópia 
de toda a árvore de objetos relacionados ao objeto que está sendo clonado é conhecido pelo termo deep copy. 
Em nosso exemplo, vamos utilizar a cópia padrão já oferecida pela classe Object. Portanto, não precisaremos 
modificar as subclasses de MsgRegistrarCliente. 
O próximo passo é criar e registrar as instâncias protótipo de cada subclasse de MsgRegistrarCliente, 
associando-as com a respectiva origem. Faremos isso criando um HashMap e associando o código da origem com a 
respectiva instância protótipo, conforme o código a seguir. Antes de criar um decodificador para mensagens XML, a 
Padrões de Projetos de Software com Java 
Marcio Quirino - 29 
 
fábrica cria as instâncias protótipo de cada subclasse, passando-as para o construtor da classe 
RegistrarClienteXMLDecoder. 
1 public RegistrarClienteDecoder createRegistrarClienteDecoder() { 
2 HashMap‹String, MsgRegistrarCliente›prototypes; 
3 prototypes.put(“X”, new MsgRegistrarCliente_X()); 
4 prototypes.put(“Y”, new MsgRegistrarCliente_Y()); 
5 prototypes.put(“Z”, new MsgRegistrarCliente_Z()); 
6 
7 return new RegistrarClienteXMLDecoder(prototypes); 
8 } 
Agora, modificamos a classe RegistrarClienteXMLDecoder, de forma que seu construtor passe a 
receber essas instâncias das subclasses de MsgRegistrarCliente, isto é, os protótipos de cada subclasse 
associados às respectivas origens. Além disso, substituímos todo o código condicional existente na versão 
anterior por uma única chamada à operação clone da instância de MsgRegistrarCliente associada à origem 
recebida como parâmetro da operação decode. 
1 public class RegistrarClienteXMLDecoder { 
2 private HashMap‹String, MsgRegistrarCliente› prototypes; 
3 
4 public RegistrarClienteXMLDecoder(HashMap‹String, MsgRegistrarCliente›prototypes) { 
5 this.prototypes = prototypes; 
6 } 
7 
8 public MsgRegistrarCliente decode(String textoMsg, String origem) { 
9 MsgRegistrarCliente prototype = prototypes.get(origem); 
10 
11 MsgRegistrarCliente msg = (MsgRegistrarCliente) prototype.clone(); 
12 
13 // … aqui viria o código de decodificação e preenchimento dos atributos 
14 // do objeto MsgRegistrarCliente 
15 return msg; 
16 } 
Você percebeu que esse código, agora, pode instanciar novas subclasses de MsgRegistrarCliente, 
sem que seja necessário modificá-lo? 
Nessa solução, a classe MsgRegistrarCliente desempenha o papel de Prototype, e cada subclasse 
de MsgRegistrarCliente desempenha o papel de ConcretePrototype. A classe RegistrarClienteXMLDecoder 
corresponde ao participante Client definido na estrutura do padrão. 
Consequências e padrões relacionados ao Prototype 
O padrão Prototype é aplicável em pelo menos três situações específicas: 
1. Quando existirem muitas fábricas específicas para a criação de diferentes famílias de 
produtos 
✓ Esse padrão permite uma solução sem que haja necessidade de implementar uma 
subclasse para cada família. Basta definir uma única classe fábrica e criar uma instância 
para cada família configurada com os protótipos que serão clonados. 
2. Quando as instâncias de uma classe forem resultado de poucas combinações de 
estado 
✓ Neste caso, é mais simples criar as instâncias típicas a priori e gerar cópias delas, ao 
invés de instanciá-las manualmente. 
3. Quando o estado de uma classe envolver muitos atributos e relacionamentos com um 
processo de criação de novas instâncias muito custoso ou complexo 
✓ Neste caso, necessitamos criar objetos com estados idênticos ou com poucas diferenças. 
Enquanto o padrão Factory Method define uma hierarquia de classes de criação paralela às classes 
produto que são instanciadas, o padrão Prototype substitui essa hierarquia e a chamada a um método fábrica 
pelo registro de uma instância protótipo e sua posterior clonagem. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 30 
 
Atenção 
O padrão Prototype permite a criação de fábricas flexíveis que podem ter sua configuração de instâncias 
definida e modificada em tempo de execução, ao contrário da solução dada pelo padrão Abstract Factory, que é 
estática. 
Em contrapartida, o padrão Prototype demanda que cada subclasse do produto a ser instanciado 
implemente a operação clone, o que pode ser complexo ou difícil, especialmente nos casos de utilização de 
classes de terceiros ou compartilhadas com outros sistemas. 
Além disso, os efeitos colaterais oriundos de uma cópia baseada em uma estratégia shallow copy e 
a complexidade de implementar uma estratégia deep copy, especialmente quando existir uma árvore 
complexa de relacionamentos ou relacionamentos circulares, podem trazer dificuldades à implementação 
desse padrão. 
Intenção do padrão Singleton 
O propósito do padrão Singleton é garantir que exista uma (e apenas uma) instância de uma classe, 
provendo um ponto de acesso global a essa instância. 
Problema do padrão Singleton 
Suponha uma situação na qual você queira garantir que apenas uma instância de uma classe possa 
existir em determinado processo, como, por exemplo, no gerenciamentode recursos como cache de objetos, 
log, conexões com banco de dados e objetos que representem recursos compartilhados por todo o processo. 
Uma possível solução seria definir uma variável global, referenciando o objeto a ser compartilhado. 
Dessa forma, todos os módulos que precisassem desse objeto fariam o acesso via essa variável global 
compartilhada. O problema é que nada impediria outros módulos de criar múltiplas instâncias dessa classe. 
Outra solução seria definir com o escopo de classe todas as operações da classe cujo objeto único 
deve ser compartilhado. Em Java, isso significa definir todas as operações da classe com o modificador 
static. Essa solução, porém, não é flexível, pois não admite a definição de subclasses e a utilização de 
polimorfismo. 
Polimorfismo 
Princípio do modelo orientado a objetos, pelo qual duas ou mais subclasses de uma mesma superclasse podem 
conter métodos com a mesma assinatura, mas com implementações diferentes, resultando em comportamentos 
especializados para cada subclasse. 
Solução do padrão Singleton 
O diagrama a seguir apresenta a estrutura do padrão Singleton. 
O nome Singleton representa o nome da classe que você deseja que tenha apenas uma instância. 
O atributo unicaInstancia é uma referência a essa única instância a ser compartilhada pelos demais 
módulos. 
O construtor dessa classe deve ser privativo, garantindo que outros módulos não possam instanciá-
la diretamente. Tanto a operação Instancia quanto o atributo unicaInstancia são propriedades com escopo 
de classe (static). 
Padrões de Projetos de Software com Java 
Marcio Quirino - 31 
 
 
Um possível uso do padrão Singleton consiste na implementação do padrão Abstract Factory. Veja, 
no código a seguir, a implementação de uma fábrica concreta utilizando o padrão Singleton. A instância 
compartilhada é referenciada pelo atributo factory, definido com o modificador static. O construtor da classe 
é definido como private, o que impede que ela seja diretamente instanciada em outros módulos. A operação 
getFactory retorna a instância única compartilhada. 
1 public class XMLDecoderFactory extends DecoderFactory { 
2 private static DecoderFactory factory = null; 
3 private XMLDecoderFactory() { 
4 } 
5 public static DecoderFactory getFactory() { 
6 if (factory == null) 
7 factory = new XMLDecoderFactory(); 
8 return factory; 
9 } 
10 public abstract RegistrarClienteDecoder createRegistrarClienteDecoder() { 
11 return new RegistrarClienteXMLDecoder(); 
12 } 
13 public abstract RegistrarContaDecoder createRegistrarContaDecoder() { 
14 return new RegistrarContaXMLDecoder(); 
15 } 
Podemos definir a fábrica abstrata como um registro dos diversos singletons correspondentes às 
fábricas concretas. 
Veja, no exemplo a seguir, que as fábricas concretas são registradas em um HashMap codificado 
pela origem (X, Y ou Z). Cada entrada dessa estrutura de dados associa uma origem ao singleton da 
respectiva fábrica concreta. A operação getInstance acessa essa estrutura para retornar a fábrica concreta 
correspondente à origem recebida como parâmetro. 
1 public abstract class DecoderFactory { 
2 private static HashMap‹String, DecoderFactory› factoryMap; 
3 
4 static { 
5 factoryMap = new HashMap‹›(); 
6 factoryMap.put(“X”, XMLDecoderFactory.getInstance()); 
7 factoryMap.put(“Y”, CSVDecoderFactory.getInstance()); 
8 factoryMap.put(“Z”, TextoLivreDecoderFactory.getInstance()); 
9 } 
10 
11 public static DecoderFactory getInstance(String origem) { 
12 return factoryMap.get(origem); 
13 } 
14 
15 public abstract RegistrarClienteDecoder createRegistrarClienteDecoder(); 
16 public abstract RegistrarContaDecoder createRegistrarContaDecoder(); 
17 } 
Consequências e padrões relacionados ao Singleton 
O padrão Singleton permite o acesso controlado a uma única instância de uma classe, sendo uma 
solução superior à utilização de variáveis globais. Permite, inclusive, a criação de subclasses mais 
específicas sem impacto para os módulos que utilizam a instância Singleton. 
O padrão Singleton é frequentemente utilizado em conjunto com o padrão Abstract Factory, conforme 
ilustrado no exemplo anterior. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 32 
 
Entretanto, após o surgimento de abordagens fortemente baseadas na construção de testes unitários 
automatizados e na aplicação de princípios como o da inversão de dependências, o padrão Singleton passou 
a ser visto como um potencial problema. Ele pode dificultar a implementação de testes unitários, visto que a 
unidade a ser testada pode estar acoplada a Singletons que dificultam o isolamento da unidade em relação 
às suas dependências. 
Além disso, existem linguagens que permitem quebrar o objetivo original do padrão, pois construções 
como reflection e serialização permitem a criação independente de objetos de classes Singleton. 
Portanto, esse é um padrão que deve ser utilizado apenas em casos muito específicos para não criar 
acoplamentos desnecessários que tornem a estrutura do software menos flexível e dificultem o processo de 
testes e depuração dos módulos. 
5. Conclusão 
Considerações Finais 
Neste conteúdo, vimos como os padrões de projeto GoF de criação podem ser usados em soluções 
de projeto de software mais flexíveis e menos acopladas. 
O padrão Factory Method é baseado em um modelo em que as subclasses implementam uma 
interface padrão definida na superclasse para a instanciação dos objetos específicos. Os padrões Abstract 
Factory, Builder e Prototype delegam a responsabilidade pela criação de objetos para classes específicas 
com essa finalidade. O padrão Abstract Factory sugere a criação de uma hierarquia de fábricas responsável 
pela instanciação de uma hierarquia paralela de produtos. 
O padrão Builder é aplicável na construção de objetos complexos, compostos por muitas partes e 
com um processo de construção custoso e complexo, isolando os módulos clientes dessa complexidade. 
O padrão Prototype é baseado na geração de cópias de objetos protótipos pré-fabricados e mais 
voltado para a composição de objetos prontos, ao contrário do Abstract Factory, que é baseado em uma 
estrutura estática de hierarquia de classes. 
Por fim, o padrão Singleton já foi bastante utilizado, mas, atualmente, é considerado por muitos um 
antipadrão, isto é, uma solução inadequada e que deve ser evitada, com exceção de situações muito 
específicas de gerenciamento de recursos que não podem ser utilizados de forma simultânea. 
Referências 
GAMMA, E.; HELM, R.; JOHNSON, R.; VLISSIDES, J. Design Patterns: Elements of Reusable 
Object-Oriented Software. 1. ed. Boston: Addison-Wesley, 1994. 
MARTIN, R. C. Clean Architecture: A Craftsman´s Guide to Software Structure and Design. 1. ed. 
Upper Saddle River, NJ: Prentice Hall, 2017. 
METSKER, S. J.; WAKE, W. C. Design Patterns in Java. 1.ed. Boston: Addison-Wesley, 2006. 
Explore+ 
Para saber mais sobre a programação orientada a objetos, acesse o site da DevMedia e leia o artigo 
intitulado Utilização dos princípios SOLID na aplicação de padrões de projeto. 
O site “Padrões de projeto/Design patterns – Refactoring.Guru” apresenta um conteúdo interativo e 
bastante completo de todos os padrões GoF com exemplos de código em diversas linguagens de 
programação. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 33 
 
Além dos padrões GoF tradicionais, outros padrões voltados para o desenvolvimento de aplicações 
corporativas em Java EE podem ser encontrados no livro Java EE 8 Design Patterns and Best Practices, 
escrito por Rhuan Rocha e João Purificação. A obra aborda padrões para interface com o usuário, lógica do 
negócio, integração de sistemas, orientação a aspectos, programação reativa e microsserviços. 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 34 
 
Padrões GoF Estruturais 
Descrição 
Apresentação de padrões GoF de projeto estruturais: Adapter,Bridge, Decorator, Composite, 
Facade, Flyweight, Proxy. 
Propósito 
Compreender os padrões de projeto GoF ligados à estruturação de objetos e identificar 
oportunidades para a sua aplicação na programação orientada a objetos são habilidades importantes para 
um projetista de software, pois, sem elas, as soluções geradas podem se tornar inflexíveis e dificultar a 
evolução dos sistemas em prazo e custo aceitáveis. 
Preparação 
Antes de iniciar o conteúdo, é recomendado instalar em seu computador um programa que permita 
elaborar modelos sob a forma de diagramas da UML (Linguagem Unificada de Modelagem). Nossa sugestão 
inicial é o Free Student License for Astah UML, usado nos exemplos deste estudo. Para isso, será necessário 
usar seu e-mail institucional para ativar a licença. 
Preencha os dados do formulário no site do software, envie e aguarde a liberação de sua licença em 
seu e-mail institucional. Ao receber a licença, siga as instruções do e-mail e instale o produto em seu 
computador. Os arquivos Astah com diagramas UML utilizados neste conteúdo estão disponíveis para 
download. 
Sugestões de links adicionais de ferramentas livres para modelagem de sistemas em UML (UML 
Tools) podem ser encontradas em buscas na Internet. 
Além disso, recomendamos a instalação de um ambiente de programação em Java. O ambiente 
recomendado para iniciantes em Java é o Apache Netbeans, cujo instalador pode ser encontrado no site do 
ambiente, acessando o menu Download. Porém, antes de instalar o Netbeans, é necessário ter instalado o 
JDK (Java Development Kit) referente à edição Java SE (Standard Edition), que pode ser encontrado no site 
da Oracle Technology Network: Java SE - Downloads | Oracle Technology Network | Oracle. 
Introdução 
Os padrões GoF, do inglês “Gang of Four”, são padrões de projeto orientados a objetos divididos em 
três categorias: de criação, estruturais e comportamentais. São assim denominados por terem sido 
introduzidos pelos quatro autores do livro Design Patterns: Elements of Reusable Object-Oriented Software 
(GAMMA et al., 1994). Os padrões de projeto GoF estruturais descrevem estratégias para compor classes 
e objetos de modo a compor estruturas maiores, que podem ser formadas com soluções baseadas em 
herança ou em composição de objetos. Neste conteúdo, você aprenderá os sete padrões deste grupo: 
Adapter, Bridge, Decorator, Composite, Facade, Flyweight e Proxy. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 35 
 
 
O padrão Adapter permite que módulos de um sistema possam interagir com diferentes 
implementações de um mesmo serviço por meio de uma interface genérica. O padrão Bridge separa a 
abstração de um objeto da sua implementação, de modo que ambos possam variar de forma independente. 
O padrão Decorator descreve uma maneira de adicionar, dinamicamente, responsabilidades a objetos por 
meio de estruturas de composição, em vez de soluções baseadas em herança. O padrão Composite 
descreve como podemos estruturar uma hierarquia de objetos construída a partir de dois tipos de elementos: 
primitivos e compostos. O padrão Facade propõe a criação de uma interface de alto nível para um 
subsistema, de modo a isolar os módulos clientes do conhecimento da estrutura interna desse subsistema. 
O padrão Flyweight apresenta uma solução para o problema de compartilhamento de um número 
elevado de pequenos objetos, com o objetivo de utilizar os recursos de memória de forma mais racional. Por 
fim, o padrão Proxy consiste na criação de um objeto substituto do real destino de uma requisição, sendo 
útil para abstrair a complexidade na comunicação entre objetos distribuídos ou para lidar com a criação sob 
demanda de um objeto com grande consumo de recursos, como imagens, por exemplo. 
1. Padrão de projeto Adapter 
Intenção do Padrão Adapter 
Adapter é um padrão cujo propósito é converter a interface de uma classe existente em outra 
interface esperada pelos módulos clientes. 
O padrão Adapter é especialmente útil quando estamos desenvolvendo um sistema que precisa 
interagir com um serviço implementado por terceiros, que forneçam componentes e interfaces diferentes e 
incompatíveis entre si. 
Problema Resolvido pelo Padrão Adapter 
• Imagine um turista que esteja viajando do Brasil para os EUA e depois para alguns países da 
Europa. 
• Ele certamente vai precisar carregar o seu telefone celular durante a viagem, não é mesmo? 
• Entretanto, o padrão de tomada brasileiro é diferente do norte-americano e do europeu. O que 
ele precisa levar na bagagem para poder plugar o seu carregador em tomadas com diferentes 
padrões? 
• Isso mesmo! Ele vai precisar de um ou mais adaptadores de tomada. 
Mas qual é a relação entre tomadas e o desenvolvimento de software? 
Padrões de Projetos de Software com Java 
Marcio Quirino - 36 
 
• Suponha que você esteja desenvolvendo um sistema para vendas on-line de diferentes 
varejistas, sendo que cada loja pode utilizar uma ou mais das soluções de pagamento disponíveis 
no mercado. 
• Cada fornecedor de solução de pagamento permite, por exemplo, a realização de uma transação 
em cartão de crédito, por meio de uma chamada à sua API de pagamento. 
• Portanto, o software de vendas deve ser capaz de ser plugado a diferentes APIs de pagamento 
que oferecem basicamente o mesmo serviço: intermediar as diversas formas de pagamento 
existentes no mercado. 
• O problema é que cada API tem uma interface proprietária definida pelo fornecedor. 
Gostaríamos que nosso software de vendas pudesse trabalhar com novos fornecedores que 
apareçam no mercado, sem que seja necessário alterar módulos já existentes, mas apenas adicionando 
novos módulos. 
O código a seguir apresenta um exemplo hipotético e simplificado de duas soluções de pagamento 
disponíveis no mercado. 
A. A primeira oferece uma classe de nome PagXPTO com duas operações distintas para o 
pagamento, conforme a bandeira do cartão (Visa ou Mastercard). 
B. A segunda fornece a classe ABCPagamentos com apenas uma operação de pagamento. 
Perceba que, além de nomes distintos, os parâmetros dessas operações também são diferentes. 
// Solução de pagamentos do fornecedor XPTO 
public class PagXPTO { 
public void pagarCartaoVisa (DadosCartao cartao, BigDecimal valor); 
public void pagarCartaoMastercard (DadosCartao cartao, BigDecimal valor); 
} 
// Solução de pagamentos do fornecedor ABC 
public class ABCPagamentos { 
public void pagarEmCartaoCredito (String numeroCartao, String nome, String CVV, String 
validade, BigDecimal valor); 
} 
Sem a utilização do padrão Adapter, um módulo cliente que precisasse chamar uma API de 
pagamento poderia ser implementado da forma esquemática ilustrada pelo código a seguir. 
public class ServicoPagamento { 
// o parâmetro nomeBroker define a API a ser chamada: XPTO ou ABC 
public void pagarCartaoCredito (String nomeBroker, CartaoCredito cartao, BigDecimal 
valor) { 
if (“XPTO”.equals(nomeBroker)) { 
pagarCartaoXPTO(cartao, valor); 
} else if (“ABC”.equals(nomeBroker)) { 
pagarCartaoABC(cartao, valor); 
} 
} 
 
// chamada à API de pagamento do fornecedor XPTO 
private void pagarCartaoXPTO(CartaoCredito cartao, BigDecimal valor) { 
PagXPTO brokerPagamento = new PagXPTO(); 
// converte o parâmetro cartao para a estrutura requerida pela API 
DadosCartao dadosCartao = converterCartao(cartao); 
// com base no número do cartão, define a função da API a ser chamada 
if (isCartaoVisa(cartao)) { 
brokerPagamento.pagarCartaoVisa(dadosCartao, valor); 
} else { 
brokerPagamento.pagarCartaoMastercard(dadosCartao, valor); 
} 
} 
 
// chamada à API de pagamento do fornecedor ABC 
private void pagarCartaoABC(CartaoCredito cartao, BigDecimal valor) { 
ABCPagamentos brokerPagamento = new ABCPagamentos(); 
Padrões de Projetos de Software com Java 
Marcio Quirino - 37 
 
brokerPagamento.pagarEmCartaoCredito(cartao.numero(), cartao.nome(), 
cartao.cvv(), cartao.validade(),valor); 
} 
} 
Note que, como cada API possui uma interface específica, o código para a realização do pagamento 
em cartão de crédito precisa ser específico para cada fornecedor, sendo implementado pelos métodos 
pagarCartaoXPTO e pagarCartaoABC. 
Você consegue visualizar como ficaria esse código com dezenas de fornecedores e de operações 
além do pagamento em cartão de crédito? 
O resultado seria um código enorme, complexo e teria que ser modificado a cada nova operação ou 
fornecedor. 
Portanto, na analogia com o carregador de telefone celular, o módulo cliente corresponde ao 
carregador, enquanto os fornecedores ABC e XPTO correspondem aos diferentes tipos de tomada. 
Precisamos de uma solução que nos permita conectar o módulo cliente com qualquer fornecedor de serviço 
de pagamento. 
Solução do Padrão Adapter 
O Adapter é um padrão cuja estrutura é apresentada no diagrama UML a seguir. 
 
O participante Client representa um módulo cliente, enquanto Adaptee representa uma 
implementação específica do serviço. Em vez de o módulo Client utilizar diretamente essa implementação 
específica, o padrão sugere o uso de uma abstração, representada pelo participante Target, que define uma 
interface comum a todas as implementações. 
Comentário 
Para cada Adaptee específico, deve ser criado um adaptador, representado pelo participante Adapter, que 
traduz a chamada genérica definida na interface Target (operation) em uma ou mais chamadas da respectiva 
implementação específica (specificOperation). 
A imagem a seguir ilustra a aplicação desse padrão no exemplo dos fornecedores de API de 
pagamentos. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 38 
 
 
Nesse exemplo, cada fornecedor (ABCPagamentos e PagXPTO) corresponde a uma ocorrência do 
participante Adaptee, definindo uma API específica que não pode ser alterada. 
A interface BrokerPagamento representa a generalização dos serviços oferecidos pelos diferentes 
fornecedores e corresponde ao participante Target do padrão. 
Os demais módulos do sistema utilizarão apenas essa interface, ficando isolados do fornecedor 
concreto da implementação desses serviços. BrokerABCAdapter e BrokerXPTOAdapter são classes que 
representam o papel Adapter do padrão e são responsáveis por traduzir a interface genérica definida em 
BrokerPagamento em chamadas específicas definidas pelos fornecedores ABCPagamentos e PagXPTO, 
respectivamente. 
Comentário 
Cada fornecedor possui um adaptador específico, assim como existem adaptadores específicos para cada 
padrão de tomada elétrica. 
A implementação a seguir mostra a estrutura da implementação com a utilização do padrão. 
A classe ServicoPagamento, que representa um módulo cliente de uma solução de pagamento, 
recebe um objeto (broker) que implementa a interface genérica de pagamento (BrokerPagamento), ficando, 
portanto, isolada das implementações específicas. Novas soluções de pagamento podem ser acrescentadas 
ao produto sem haver necessidade do módulo cliente ser alterado. 
Para cada fornecedor de API, foi criada uma classe Adapter que implementa o mapeamento da 
interface genérica nas chamadas específicas da respectiva API. 
Observe como o código, que na solução anterior estava todo concentrado no módulo cliente, deu 
origem aos adaptadores para cada fornecedor de API de pagamento. 
public class ServicoPagamento { 
// o parâmetro BrokerPagamento é um objeto (adapter) que implementa a interface 
genérica 
// BrokerPagamento 
public void pagarCartaoCredito (BrokerPagamento broker, CartaoCredito cartao, 
BigDecimal valor) { 
broker.pagarCartaoCredito(cartao, valor); 
} 
} 
Padrões de Projetos de Software com Java 
Marcio Quirino - 39 
 
 
public class BrokerXPTOAdapter implements BrokerPagamento ( 
public void pagarCartaoCredito(CartaoCredito cartao, BigDecimal valor) { 
PagXPTO brokerPagamento = new PagXPTO(); 
DadosCartao dadosCartao = converterCartao(cartao); 
if (isCartaoVisa(cartao)) { 
brokerPagamento.pagarCartaoVisa(dadosCartao, valor); 
} else { 
brokerPagamento.pagarCartaoMastercard(dadosCartao, valor); 
} 
} 
} 
 
public class BrokerABCAdapter implements BrokerPagamento { 
public void pagarCartaoCredito(CartaoCredito cartao, BigDecimal valor) { 
ABCPagamentos brokerPagamento = new ABCPagamentos(); 
brokerPagamento.pagarEmCartaoCredito(cartao.numero(), cartao.nome(), 
cartao.cvv(), cartao.validade(), valor); 
} 
} 
Consequências e padrões relacionados ao padrão Adapter 
O padrão Adapter permite incorporar módulos previamente desenvolvidos, modificando sua interface 
original, sem que haja necessidade de alterá-los. Esse tipo de solução possibilita a utilização de diferentes 
implementações proprietárias compartilhando uma única interface, isolando, portanto, os módulos clientes 
das diferentes interfaces proprietárias. 
O esforço envolvido na implementação de um Adapter depende do grau de semelhança que exista 
entre a interface genérica Target e a interface específica do componente para o qual o Adapter esteja sendo 
construído. Quanto maior a semelhança, menor o esforço e vice-versa. 
Comentário 
O participante Target pode ser definido como uma interface puramente abstrata ou como uma classe abstrata, 
caso os diversos adaptadores possuam métodos em comum ou as operações compartilhem uma sequência comum 
de passos, em que cada passo tenha uma implementação específica em cada adaptador. 
Neste último caso, portanto, as operações com uma sequência comum seriam implementadas na 
superclasse da qual os adaptadores específicos herdariam, aplicando-se conjuntamente o padrão Template 
Method. 
Comentário 
O padrão Bridge tem uma estrutura similar ao padrão Adapter, porém, com um objetivo diferente, que é separar 
a interface da sua implementação, de modo que elas possam mudar de forma independente, sendo aplicado quando 
estamos implementando tanto os módulos clientes, como os fornecedores. O padrão Adapter, por sua vez, visa oferecer 
uma diferente interface para um ou mais componentes existentes, tipicamente fornecidos por terceiros. 
2. Padrões de projeto Bridge e Decorator 
Intenção do padrão Bridge 
O objetivo principal do padrão Bridge é desacoplar uma abstração da sua implementação, permitindo 
que ambas possam variar de forma independente. 
O padrão Bridge é, tipicamente, utilizado para evitar um problema de explosão combinatória de 
classes, resultante de soluções baseadas em herança, especialmente em hierarquias que precisam 
combinar aspectos do domínio (abstração) com outros específicos de implementação, tais como 
plataformas, tecnologias, estruturas de dados etc. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 40 
 
Problema resolvido pelo padrão Bridge 
Normalmente, quando uma abstração pode ter diferentes implementações com código em comum 
entre elas, a solução costuma ser a definição de uma hierarquia na qual o código comum é estabelecido na 
superclasse abstrata e cada implementação específica é definida em uma subclasse concreta. 
Entretanto, esse tipo de solução não é flexível para comportar diferentes classificações da mesma 
abstração, pois cada classificação demanda uma hierarquia própria. O resultado é que a combinação dessas 
diferentes classificações por meio de herança gera um número enorme de classes, tornando o projeto 
complexo e inflexível. 
Esse problema costuma ocorrer quando combinamos diferentes perspectivas de classificação de 
uma abstração. 
Exemplo 
Podemos combinar a perspectiva do domínio com a de plataforma, como ocorre na implementação de 
frameworks de interface gráfica com o usuário, pois os elementos visuais, como painéis, botões e caixas de texto 
correspondem às abstrações que precisam ser implementadas em diferentes plataformas, tais como Windows, Motif 
etc. 
A imagem a seguir apresenta um exemplo hipotético de um componente textual (TextComponent), 
que possui uma implementação específica para Windows e outrapara Motif. 
Você consegue perceber que o aspecto de variação entre as duas subclasses de 
TextComponent é a plataforma? 
 
A solução seria aceitável se houvesse apenas essa perspectiva de variação. Porém, podem existir 
especializações de um componente textual inerentes ao domínio da abstração, tais como uma área de texto 
(TextArea) e um campo de texto (TextField), conforme ilustrado pelo diagrama a seguir. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 41 
 
 
Você consegue visualizar que existem especializações do componente texto próprias do 
domínio de interface gráfica, ao mesmo tempo em que existem especializações do componente texto 
decorrentes das diferentes plataformas de implementação da interface gráfica? 
O problema agora é que tanto um TextField quanto um TextArea precisam ser implementados nas 
duas plataformas: Windows e Motif. 
Qual seria a solução mais adequada? 
Uma solução baseada na combinação dessas classificações é ilustrada no diagrama UML a seguir. 
Comentário 
Perceba que esse caminho de solução gera uma enorme combinação de classes, misturando aspectos do 
domínio com aspectos de implementação e resultando em uma estrutura complexa e inflexível. 
 
Portanto, o problema que o padrão Bridge soluciona é a separação da abstração (TextComponent e 
suas especializações do domínio, TextArea e TextField) dos aspectos de sua implementação (plataformas 
Padrões de Projetos de Software com Java 
Marcio Quirino - 42 
 
Windows e Motif), permitindo que ambas possam evoluir separadamente, isto é, podemos adicionar novos 
tipos de componentes e novas plataformas sem gerar um problema combinatório de difícil gestão. 
Solução do padrão Bridge 
A estrutura da solução proposta pelo padrão Bridge está representada no diagrama de classes a 
seguir: 
 
Comentário 
A ideia proposta pelo padrão é separar a hierarquia de abstrações da hierarquia de implementações, fazendo 
com que a implementação das operações na abstração (participante Abstraction) seja responsável por delegar a 
execução para a implementação específica que estiver conectada à abstração (participante ConcreteImplementor). 
Veja na imagem a seguir como esse padrão é aplicado na implementação do Java AWT (Abstract 
Window Toolkit), framework básico de componentes de interface gráfica com o usuário da linguagem Java. 
 
Perceba que a hierarquia à esquerda corresponde à abstração dos componentes do domínio de 
interface gráfica definida pelo participante Abstraction na estrutura do padrão Bridge, enquanto a hierarquia 
à direita corresponde aos aspectos de implementação dessa abstração nas diversas plataformas. 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 43 
 
Atenção 
Cada componente na abstração de elementos visuais do AWT possui uma ponte para o elemento peer 
correspondente, sendo que cada elemento folha da hierarquia peer possui implementação específica para uma 
plataforma. 
Desse modo, um componente TextArea, por exemplo, possui uma operação insert, cuja 
implementação é responsável por chamar a operação insert do elemento peer associado. Neste caso, o 
componente TextAreaPeer possui duas implementações específicas: uma implementação Windows 
(WTextAreaPeer) e uma implementação Motif (MTextAreaPeer). 
O código a seguir é um extrato da implementação desses componentes no AWT. 
Comentário 
Note que o método insertText, chamado pela operação insert da classe TextArea, acessa o elemento peer 
definido no ancestral Component e faz o downcasting desse elemento para TextAreaPeer, pois esse elemento é 
definido com o tipo genérico ComponentPeer. Na sequência, a operação insert do peer associado é chamada. 
Desse modo, a implementação do componente TextArea não tem qualquer menção à plataforma 
específica. A classe WTextAreaPeer, por sua vez, implementa a operação insert chamando um código nativo 
para Windows, que atualizará o texto do componente na interface gráfica do Windows. 
public class TextArea extends TextComponent { 
public void insert (String str, int pos) { 
insertText (str, pos); 
} 
public void insertText (String str, int pos) { 
// insere o texto na posição indicada 
String tmp1 = getText().substring(0, pos); 
String tmp2 = getText().substring(pos, getText().length()); 
// chama o método setText da superclasse para atualizar o novo conteúdo 
setText (tmp1 + str + tmp2); 
// obtém o elemento peer associado específico de plataforma 
TextAreaPeer peer = (TextAreaPeer) super.getPeer(); 
// chama a operação do componente peer para renderizar o texto na plataforma 
alvo 
if (peer != null) { 
peer.insert(str, pos); 
} 
} 
} 
 
// classe TextArea na plataforma alvo Windows 
public class WTextAreaPeer implements TextAreaPeer { 
public void insert(String text, int pos) { 
replaceRange(text, pos, pos); 
} 
 
// código nativo específico da plataforma Windows 
public native void replaceRange(String text, int start, int end); 
} 
Consequências e padrões relacionados ao padrão Bridge 
O padrão Bridge possibilita o desacoplamento de uma hierarquia de abstrações em relação a uma 
hierarquia de implementações associadas, o que promove a extensibilidade da solução, pois podemos 
acrescentar, de forma independente, tanto novos itens à abstração quanto novas implementações 
associadas. 
Os objetos da hierarquia de implementação precisam ser criados e plugados nas respectivas 
abstrações. 
Para promover o isolamento entre as abstrações e as suas implementações específicas, o padrão 
Abstract Factory pode ser utilizado para criar e configurar uma ponte entre as duas hierarquias. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 44 
 
Comentário 
A estrutura de solução do padrão Bridge é similar à do padrão Adapter. A diferença fundamental é que o padrão 
Adapter é aplicado quando desejamos incorporar à solução elementos de terceiros, já prontos, que não podem ser 
modificados e que precisam ser acessados a partir de uma interface comum. Desse modo, no padrão Adapter, a 
interface comum é tipicamente definida a posteriori, ou seja, a partir de implementações já existentes que possuem 
interfaces distintas e incompatíveis entre si. O padrão Bridge, por outro lado, é uma solução definida a priori, ou seja, 
a interface comum de implementação é determinada de forma a permitir que diferentes implementações possam ser 
construídas a partir da sua definição. 
Intenção do padrão Decorator 
Decorator é um padrão que permite adicionar responsabilidades a um objeto de forma dinâmica e 
mais flexível do que utilizando subclasses. 
Problema resolvido pelo padrão Decorator 
Uma forma comum de estender a funcionalidade de classes é por meio da criação de subclasses. 
Exemplo 
Imagine que você queira adicionar às classes de leitura e escrita de arquivos texto a capacidade de criar e ler 
arquivos texto criptografados e compactados. 
Poderíamos resolver esse problema por meio de herança das classes do pacote Java I/O, criando 
uma estrutura como a ilustrada no código a seguir. O pacote Java I/O define a classe FileWriter, que oferece 
operações para a escrita de arquivos texto. Definimos, então, a classe EncryptedFileWriter como uma 
subclasse de FileWriter e sobrescrevemos o método write, adicionando a capacidade de criptografar o texto 
antes que seja escrito no arquivo. 
public class EncryptedFileWriter extends FileWriter { 
public EncryptedFileWriter(File file) throws IOException { 
super(file); 
} 
 
public void write(String text) throws IOException { 
String encryptedText = encrypt(text); 
super.write(encryptedText); // comanda a gravação em disco via FileWriter 
} 
 
private String encrypt(String text) { 
String result = text; 
// aqui estaria o código para encriptar o texto 
return result; 
} 
} 
Para adicionar a capacidade de criar arquivos texto compactados, podemos criar uma outra extensão 
da classe FileWriter, denominada CompressedFileWriter, sobrescrevendoos métodos write e close. Nessa 
solução simplificada, supomos que a compactação ocorre somente no momento do fechamento do arquivo. 
public class CompressedFileWriter extends FileWriter { 
StringBuilder buffer = new StringBuilder(); 
 
public CompressedFileWriter(File file) throws IOException { 
super(file); 
} 
public void write(String text) throws IOException { 
buffer.append(text); 
} 
public void close() throws IOException { 
// compacta o conteúdo no instante em que o arquivo vai ser fechado 
char[] compressedBuffer = compress(buffer.toString()); 
super.write(compressedBuffer); // comanda a gravação em disco via FileWriter 
super.close(); 
Padrões de Projetos de Software com Java 
Marcio Quirino - 45 
 
} 
private char[] compress(String buffer) { 
// algoritmo de compressão implementado aqui 
// retorna resultado da compressão em um array 
} 
} 
Soluções baseadas em herança para problemas dessa natureza são pouco flexíveis. 
Você consegue identificar por que a solução dada para a criptografia e a compressão de arquivos 
texto nos exemplos anteriores é pouco flexível? 
Agora, imagine que você queira gerar um arquivo texto criptografado e compactado. Poderíamos 
definir a classe CompressedFileWriter como uma subclasse da classe EncryptedFileWriter, em vez de 
FileWriter. Porém, cairíamos em outra situação problemática, pois não poderíamos gerar um arquivo apenas 
compactado e sem criptografia, a menos que adicionássemos complexidade ao projeto por meio de 
parâmetros de controle como setCryptoON, setCryptoOFF, por exemplo. 
Portanto, o problema resolvido pelo padrão Decorator consiste em adicionar novos comportamentos 
às funcionalidades já existentes em uma determinada classe, sem que seja necessário alterar o código 
dessa classe ou criar subclasses, de modo que esses elementos possam ser combinados de maneiras 
diferentes, conferindo flexibilidade à solução. 
Solução do padrão Decorator 
A estrutura da solução proposta pelo padrão Decorator está representada no diagrama de classes a 
seguir: 
 
O participante Component define uma interface implementada pelas classes concretas, 
correspondentes ao participante ConcreteComponent, que podem ter comportamentos adicionados a elas 
dinamicamente. O participante Decorator também é uma especialização de Component e, portanto, todas 
as operações definidas em Component podem ser sobrescritas pelos elementos representados pelo 
participante Decorator. Além disso, comportamentos adicionais são definidos nas implementações concretas 
do Decorator, representadas pelos participantes ConcreteDecoratorA e ConcreteDecoratorB. 
 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 46 
 
Atenção 
Cada Decorator tem uma referência para o elemento Component, ao qual ele está adicionando funcionalidades, 
funcionando como uma espécie de envoltório ao componente original. 
As classes do pacote Java I/O utilizam amplamente essa solução, conforme ilustrada por alguns 
elementos presentes no diagrama UML, a seguir: 
 
• Writer é uma classe abstrata que corresponde ao participante Component do padrão. 
• As classes OutputStreamWriter e FileWriter desempenham o papel de ConcreteComponent e 
implementam as operações de escrita em um arquivo texto. 
• As classes BufferedWriter e PrintWriter correspondem ao participante ConcreteDecorator, 
adicionando funcionalidades a algum objeto que implemente a definição da classe Writer. 
Note que os desenvolvedores optaram por não implementar uma superclasse comum no papel de 
Decorator, fazendo com que ambas as classes tenham uma referência para o Writer que está sendo 
decorado. 
A classe BufferedWriter adiciona a um Writer a capacidade de manter uma área de armazenamento 
interno em memória e somente comandar a escrita em disco quando essa área estiver cheia, reduzindo o 
número de acessos a disco. A classe PrintWriter adiciona funcionalidades de saída formatada por meio de 
operações como printf e println. 
O código a seguir apresenta um trecho da implementação da classe BufferedWriter, utilizando o 
padrão Decorator. 
Todo Decorator recebe, no seu construtor, um parâmetro correspondente ao participante 
Component do padrão, que, neste exemplo, corresponde à classe abstrata Writer. 
A classe BufferedWriter implementa uma versão específica decorada da operação write que 
armazena os dados recebidos em um array e, somente quando a capacidade ultrapassa um limiar, ela 
chama a operação localFlush, que se responsabiliza por chamar a operação write do componente que está 
sendo decorado, fazendo efetivamente a escrita da área temporária para o arquivo. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 47 
 
public class BufferedWriter extends Writer 
private Writer out; // elemento que está sendo decorado 
char[] buffer; // área de escrita temporária em memória 
int count; // número de caracteres ocupados na área de escrita temporária 
 
public BufferedWriter(Writer out) throws IOException { 
this.out = out; 
buffer = new char[4096]; // cria uma área temporária de 4K 
} 
public void write(char[] buf, int offset, int len) throws IOException { 
if (count + len > buffer.length) { // se a área a ser escrita não couber mais 
no buffer 
localFlush(); // descarrega área para disco 
} else { 
System.arraycopy(buf, offset, buffer, count, len); // salva na área 
temporária 
count += len; // incrementa espaço ocupado 
if (count == buffer.length) // chegou no limite da área temporária? 
localFlush(); // descarrega área para disco 
} 
} 
 
public void flush() throws IOException { 
localFlush(); 
out.flush(); 
} 
 
protected void localFlush() throws IOException { 
if (buffer.length > 0) 
out.write(buffer, 0, count); // chama operação write do componente 
decorado 
} 
} 
O código a seguir mostra como essas classes podem ser utilizadas em conjunto. Note como o objeto 
writer é decorado por um BufferedWriter e por um PrintWriter, que adicionam as funcionalidades de 
armazenamento em área temporária e saída formatada, respectivamente, ao objeto FileWriter base. Em vez 
de herança, um esquema de composição sucessiva é utilizado, com o objeto base sendo passado como 
parâmetro para o construtor do elemento que adiciona novas funcionalidades. Desse modo, um objeto 
FileWriter é passado ao construtor da classe BufferedWriter que, por sua vez, é passado ao construtor da 
classe PrintWriter. 
public class Exemplo { 
public static void main(String[] args) { 
try (FileWriter writer = new FileWriter("app. log"); 
BufferedWriter bw = new BufferedWriter(writer); // writer é o objeto 
base 
PrintWriter pw = new PrintWriter(bw)) { // bw é o objeto base 
 
pw.println("Exemplo de uso do decorator"); 
 
} catch (IOException e) { 
System.err.format("IOException: %s%n", e); 
} 
} 
} 
Consequências e padrões relacionados ao padrão Decorator 
O padrão Decorator oferece uma forma mais flexível de adicionar funcionalidades a objetos do que 
as soluções baseadas em herança, por ser baseado em composição de objetos. Com a utilização desse 
padrão, responsabilidades podem ser adicionadas, em tempo de execução, pela adição de camadas de 
decoradores, formando uma estrutura similar a uma cebola. 
Além disso, a solução baseada em composição possibilita a definição de uma interface mais simples 
para o objeto base (o núcleo da cebola), ao contrário das soluções baseadas em herança, em que 
Padrões de Projetos de Software com Java 
Marcio Quirino - 48 
 
frequentemente a superclasse tem que ser estendida para comportar novas operações definidas nas 
subclasses. 
Entretanto, as soluções que utilizam o padrão Decorator podem acabar utilizando muitos objetos 
pequenos, todos muito parecidos e conectados, o que pode complicar o seu entendimento e a correção de 
erros. 
O padrão Decorator tem estrutura similar à utilizada no padrão Composite, mas um decorator tem o 
propósito de adicionar responsabilidadesa outros objetos, enquanto o padrão Composite está mais ligado 
à agregação de objetos em uma estrutura hierárquica. 
Desafio 
Você consegue implementar as classes EncryptedFileWriter e CompressedFileWriter apresentadas 
anteriormente utilizando o padrão Decorator? 
3. Padrões de projeto Composite e Facade 
Intenção do padrão Composite 
O padrão Composite permite representar hierarquias de composição de objetos em uma estrutura 
de árvore, viabilizando que a manipulação dos objetos individuais e dos agregados possa ser feita com o 
mesmo conjunto de operações. 
Problema resolvido pelo padrão Composite 
Exemplo 
Suponha que você esteja desenvolvendo um programa de envio e recepção de mensagens texto que devem 
ser organizadas em pastas. Além de mensagens, pastas podem conter outras pastas. 
Podemos realizar operações com as mensagens, tais como apagar uma mensagem, criptografar 
uma mensagem, entre outras. Essas mesmas operações se aplicam às pastas, sendo que apagar uma 
pasta implica em apagar todas as mensagens e as pastas descendentes dela. 
O padrão Composite é especialmente interessante para problemas de representação de hierarquias 
de elementos nas quais observamos recursividade na execução das operações aplicadas aos agregados. 
O código apresentado a seguir ilustra a estrutura de implementação para o problema sem utilizar o 
padrão Composite. 
public class ServicoMensagem { 
public void apagar(Object elemento) { 
if (elemento instanceof Mensagem) 
apagarMensagem((Mensagem) elemento); 
else if (elemento instanceof Pasta) 
apagarPasta((Pasta) elemento); 
} 
private void apagarMensagem(Mensagem mensagem) { 
// lógica para apagar a mensagem 
} 
private void apagarPasta(Pasta pasta) { 
// lógica para apagar a pasta 
// que replicaria a lógica condicional da operação apagar 
// pois uma pasta pode conter mensagens e pastas 
} 
} 
Esse código apresenta problemas, pois, além das operações com os diferentes elementos estarem 
concentradas em uma única classe (ServicoMensagem), existe um código condicional baseado no tipo do 
objeto (mensagem ou pasta), que identifica a operação a ser executada (apagarMensagem ou apagarPasta). 
Padrões de Projetos de Software com Java 
Marcio Quirino - 49 
 
A adição de novas operações e novos tipos de objeto, certamente, aumentará a complexidade dessa 
implementação. 
Solução do padrão Composite 
A estrutura da solução proposta pelo padrão Composite está representada no diagrama de classes 
a seguir: 
 
A ideia central do padrão é representar a hierarquia de modo que todos os elementos herdem de 
uma superclasse genérica representada pelo participante Component. 
• O participante Leaf representa um elemento primitivo da hierarquia (folha), isto é, um 
elemento que não possui descendentes, enquanto o participante Composite representa os 
agrupamentos de elementos (folhas ou outros agregados). 
• O agrupamento é definido pelo relacionamento entre o agregado Composite e seus filhos do 
tipo Component. 
• O participante Client representa um módulo cliente qualquer que utiliza os elementos da 
hierarquia. 
• Nesse modelo, operation representa cada operação aplicável tanto à folha como ao 
agregado. 
• Além disso, um Composite define a implementação para operações de gerenciamento do 
agregado, permitindo adicionar e remover elementos, além de obter um elemento específico. 
A estrutura de código a seguir ilustra a aplicação do padrão no exemplo do programa de mensagens. 
public abstract class Elemento { 
public void adicionar(Elemento elem) { } 
public void remover(Elemento elem) { } 
public abstract void apagar(); 
public abstract void criptografar(); 
} 
 
public class Mensagem extends Elemento { 
Padrões de Projetos de Software com Java 
Marcio Quirino - 50 
 
public void apagar() { 
// lógica para apagar a mensagem 
} 
public void criptografar() { 
// lógica para criptografar a mensagem 
} 
} 
 
public class Pasta extends Elemento { 
private List filhos = new ArrayList(); 
public void adicionar(Elemento elem) { 
filhos.add(elem); 
} 
public void remover(Elemento elem) { 
filhos.remove(elem); 
} 
public void apagar() { 
for (Elemento elemento : filhos) // apaga todos os filhos 
elemento.apagar(); 
// lógica adicional para apagar a pasta 
} 
public void criptografar() { 
for (Elemento elemento : filhos) // criptografa todos os filhos 
elemento.criptografar(); 
// lógica adicional para criptografar a pasta 
} 
public class ServicoMensagem { 
public void apagar(Elemento elemento) { 
// código adicional de tratamento da requisição ficaria aqui 
elemento.apagar(); // apaga o elemento: pode ser uma mensagem ou uma pasta 
} 
public void criptografar(Elemento elemento) { 
// código adicional de tratamento da requisição ficaria aqui 
elemento.criptografar(); // criptografa o elemento: pode ser uma mensagem ou 
uma pasta 
} 
} 
1. A classe Elemento corresponde ao participante Component do padrão, definindo as operações 
para adicionar e remover elementos de um agregado, além das operações abstratas apagar e 
criptografar, que se aplicam tanto a uma mensagem como a uma pasta de mensagens. 
2. A classe Mensagem corresponde ao participante Leaf do padrão, definindo a implementação 
específica das operações para apagar e criptografar uma mensagem. 
3. A classe Pasta corresponde ao participante Composite do padrão, definindo a implementação 
das operações de adição e remoção de elementos à pasta, além do comportamento específico 
para as operações apagar e criptografar. 
4. Como é comum na implementação do padrão Composite, as operações apagar e criptografar do 
agregado chamam as operações de mesmo nome dos filhos, podendo adicionar algum 
tratamento específico, além daquele implementado nas folhas. 
5. ServicoMensagem representa um módulo cliente, que, simplesmente, chama a operação sem 
precisar saber se o elemento é uma Mensagem ou uma Pasta. 
Consequências e padrões relacionados ao Composite 
O padrão Composite representa uma estrutura hierárquica de objetos, permitindo que os módulos 
clientes possam manipular seus elementos de maneira uniforme por meio da mesma interface. 
Em geral, os módulos clientes não precisam saber se estão lidando com um elemento primitivo (folha) 
ou com um agregado, o que simplifica o código, pois não é necessário utilizar estruturas condicionais para 
identificar a operação a ser chamada. 
O padrão permite adicionar novos tipos de componentes sem que seja necessário alterar o código 
dos módulos clientes. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 51 
 
Por outro lado, a estrutura é muito genérica, o que dificulta a implementação de hierarquias que 
tenham regras de composição mais restritivas em relação aos tipos de elemento que um determinado 
agregado possa ter. 
Comentário 
O padrão Iterator é, muitas vezes, utilizado para fazer o percurso pelos elementos de uma hierarquia 
implementada com o padrão Composite. 
O padrão Decorator também é utilizado com frequência junto com o padrão Composite. Nesse caso, 
eles, tipicamente, herdam da mesma superclasse, fazendo com que as classes que representam o 
participante Decorator tenham que implementar a interface definida pelo participante Component. 
Intenção do padrão Facade 
O padrão Facade tem o propósito de oferecer uma interface de alto nível para um componente ou 
subsistema, de modo que os módulos clientes possam utilizá-lo mais facilmente, não precisando manipular 
uma estrutura complexa de navegação de objetos, nem ficando vulnerável a alterações nesta estrutura. 
Problema resolvido pelo padrão Facade 
Exemplo 
Imagine que você esteja implementando um sistema de comércio eletrônico que pode ser utilizado tanto em 
uma aplicação web, como em um aplicativo para dispositivo móvel. 
Uma estrutura comum para esse tipo de sistema é estratificar os componentes em camadas, 
separando em camadas distintas os elementos que compõema interface com o usuário, a lógica do negócio 
e os demais elementos dependentes de tecnologia, tais como armazenamento e recuperação de dados, 
integração com sistemas e dispositivos externos, entre outros. 
Suponha que para implementar uma parte da página de compras, o código precise manipular um 
objeto CarrinhoRepository para recuperar o carrinho do cliente, um objeto Carrinho para adicionar produtos 
ao carrinho do cliente, um objeto ProdutoRepository para recuperar produtos a serem apresentados ao 
cliente, um objeto ValidadorCarrinho para verificar se o carrinho atende às regras do negócio, um objeto 
para calcular o valor do frete até o destino definido pelo cliente e um objeto para calcular o prazo de entrega. 
A estrutura de dependências resultante dessa solução é apresentada no diagrama de classes a 
seguir. 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 52 
 
O problema dessa estrutura é que o elemento PaginaCompras, que pertence à camada de interface 
com usuário, precisa conhecer e manipular diversos elementos da camada de Lógica de Negócio, o que 
torna seu código mais complexo e suscetível a modificações que ocorram na estrutura de objetos de 
negócio. 
Comentário 
De forma geral, o problema resolvido pelo padrão Facade é permitir que um módulo cliente utilize um 
subsistema ou uma camada complexa de um sistema sem precisar conhecer ou manipular diversos elementos e, com 
isso, reduzir o acoplamento entre o subsistema e os módulos clientes. 
Solução do padrão Facade 
A estrutura da solução proposta pelo padrão Facade é apresentada no diagrama UML a seguir. 
Consiste, basicamente, em definir uma classe correspondente ao participante Facade, que oferece 
uma interface de alto nível ao participante Client, concentrando toda a manipulação dos elementos 
pertencentes à camada ou subsistema. Portanto, o elemento Facade passa a ser o ponto focal de contato 
entre o cliente e o subsistema, ou camada com o qual ele precisa se comunicar. 
 
A aplicação do padrão no problema mencionado anteriormente nos leva à estrutura definida no 
diagrama a seguir. O elemento PaginaCompras agora interage com uma interface de mais alto nível 
oferecida pelo elemento ServicoCompras. Desse modo, toda a manipulação de objetos que estava presente 
em PaginaCompras passa a ser implementada em ServicoCompras. 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 53 
 
Consequências e padrões relacionados ao Facade 
• O padrão Facade reduz a complexidade de implementação dos módulos clientes de um 
subsistema, promovendo menor acoplamento entre o subsistema e os seus clientes. 
• Isso permite que os componentes internos do subsistema possam ser modificados sem afetar os 
módulos clientes. 
• Entretanto, o padrão não impede que os módulos clientes possam acessar elementos do 
subsistema, se necessário. Normalmente, o acesso a esses elementos é promovido e controlado 
pelo módulo Facade. 
• É possível reduzir ainda mais o acoplamento entre um subsistema e os seus clientes ao 
implementar o Facade por meio de uma interface abstrata que pode ser implementada por 
diferentes classes concretas. 
Comentário 
O padrão Abstract Factory ou um framework de injeção de dependências podem ser utilizados para criar a 
implementação concreta de um subsistema, caso o Facade seja uma interface abstrata. 
O padrão Mediator possui uma estrutura similar ao Facade, contudo, tem o propósito de abstrair a comunicação 
entre objetos da mesma camada, centralizando funcionalidades que não sejam específicas de nenhum desses objetos. 
O padrão Facade, por outro lado, tem o propósito de fornecer uma interface de alto nível para clientes de outros 
subsistemas. 
4. Padrões de projeto Flyweight e Proxy 
Intenção do padrão Flyweight 
O propósito do padrão Flyweight é permitir a utilização de diversos pequenos objetos de forma 
eficiente, por meio de uma solução baseada em compartilhamento de objetos. 
Problema resolvido pelo padrão Flyweight 
Suponha que você esteja implementando uma aplicação envolvendo substâncias químicas. 
Uma substância química simples é formada por átomos de um único elemento químico, como, por 
exemplo, o hidrogênio (H2) e o enxofre (S8). Uma substância composta é formada por átomos de elementos 
químicos diferentes como, por exemplo, a água (H2O) e o bicarbonato de sódio (NaHCO3). 
Uma maneira de representar esses elementos em Java é expressa no código a seguir: 
public class ElementoQuimico { 
private String simbolo; 
private String nome; 
 
public ElementoQuimico(String simbolo, String nome) { 
this.simbolo = simbolo; 
this.nome = nome; 
} 
// getters e setters omitidos 
} 
 
public abstract class Substancia { 
private String nome; 
public Substancia(String nome) { 
this.nome = nome; 
} 
// getters e setters omitidos 
} 
 
public class SubstanciaSimples extends Substancia { 
private int atomos; 
private ElementoQuimico elemento; 
Padrões de Projetos de Software com Java 
Marcio Quirino - 54 
 
 
public SubstanciaSimples(String nome, ElementoQuimico elemento, int atomos) { 
super(nome); 
this.atomos = atomos; 
this.elemento = elemento; 
} 
// getters e setters omitidos 
} 
 
public class SubstanciaComposta extends Substancia { 
// Conjunto de elementos químicos e respectivas quantidades de átomos 
private Map composicao; 
 
public SubstanciaComposta(String nome, Map composicao) { 
super(nome); 
this.composicao = composicao; 
} 
 
// getters e setters omitidos 
} 
Um elemento químico contém os atributos símbolo e nome. 
• A classe SubstanciaSimples tem o nome herdado da superclasse e define o número de 
átomos do respectivo elemento químico. 
• A classe SubstanciaComposta define a composição de elementos químicos e suas 
respectivas quantidades de átomos em uma estrutura do tipo chave-valor (Map). 
O código a seguir mostra como objetos dessas classes poderiam ser instanciados. Note como o 
elemento químico oxigênio é instanciado repetidamente nas diferentes substâncias criadas. 
public class Exemplo { 
public static void main(String[] args) { 
SubstanciaSimples s1 = new SubstanciaSimples("Oxigênio", 
new ElementoQuimico("O", "Oxigênio"), 2); 
SubstanciaSimples s2 = new SubstanciaSimples("Ozônio", 
new ElementoQuimico("O", "Oxigênio"), 3); 
 
Map composicaoAgua = new HashMap(); 
composicaoAgua.put(new ElementoQuimico("H", "Hidrogênio"), 2); 
composicaoAgua.put(new ElementoQuimico("O", "Oxigênio"), 1); 
 
SubstanciaComposta s3 = new SubstanciaComposta("Agua", composicaoAgua); 
} 
} 
A definição de elemento químico é um exemplo característico do que denominamos objeto imutável, 
isto é, os valores de seus atributos não mudam. 
Comentário 
Considerando que um elemento químico é imutável, e supondo que precisamos criar milhares de substâncias 
na nossa aplicação de química, a solução apresentada é altamente ineficiente no uso de memória, pois serão 
instanciados milhares de pequenos objetos correspondentes aos elementos químicos. 
O problema que esse padrão tenta resolver, portanto, consiste em como podemos compartilhar 
objetos imutáveis, ou as partes imutáveis de objetos, de modo a utilizar os recursos de memória de forma 
mais eficiente. 
Solução do padrão Flyweight 
A estrutura da solução proposta pelo padrão Flyweight está representada no diagrama de classes a 
seguir. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 55 
 
 
A ideia central consiste na construção de um pool de objetos compartilhados, criado e atualizado por 
uma fábrica. Desse modo, os objetos clientes, em vez de instanciarem diretamente esses objetos, passam 
a fazer uso da fábrica. 
O participante FlyweightFactory representa a fábrica que gerencia o pool de objetos compartilháveis. 
Sempre que um cliente pedir um objeto flyweight, a fábrica retorna uma já existente ou cria uma nova,se 
ainda não existir uma. 
Os objetos flyweight estão organizados em uma hierarquia com duas subclasses: 
• ConcreteFlyweight, representando os objetos que são compartilhados. 
• UnsharedConcreteFlyweight, que representa os objetos que não são compartilhados. 
Isso acontece porque o padrão não obriga que todos os objetos que implementam a interface abstrata 
Flyweight sejam compartilhados, mas permite que os clientes fiquem isolados da decisão de compartilhar 
ou não os objetos de uma determinada classe. 
Portanto, podemos iniciar um projeto definindo que objetos de uma classe não serão compartilhados 
(assumindo o papel de UnsharedConcreteFlyweight) e depois passarmos para uma solução em que eles 
sejam compartilhados, assumindo o papel de ConcreteFlyweight, sem qualquer impacto para os módulos 
clientes. 
Comentário 
Note que um ConcreteFlyweight somente compartilha o seu estado intrínseco, isto é, um conjunto de 
propriedades imutáveis. Se os objetos compartilhados de uma classe possuírem propriedades extrínsecas, ou seja, 
que possam mudar de valor, elas deverão ser mantidas pelos módulos clientes, que poderão passar esses dados como 
parâmetros de operações do flyweight. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 56 
 
O exemplo a seguir mostra como o código das substâncias químicas pode ser escrito com a utilização 
do padrão Flyweight. Os elementos químicos, em vez de serem instanciados diretamente pelos módulos 
clientes, passam a ser instanciados pela classe ElementoQuimicoFactory, que desempenha o papel do 
participante FlyweightFactory do padrão. Ela armazena todos os elementos já criados e se necessário, cria 
novos elementos. O programa exemplo cria as substâncias solicitando os elementos para a fábrica. Desse 
modo, apenas dois elementos são instanciados (H e O), em vez de quatro, como no programa original, dado 
que o objeto representando o elemento oxigênio é compartilhado por todos os objetos que representam as 
substâncias instanciadas. 
Note que o construtor da classe ElementoQuimico não é mais público, para evitar sua instanciação 
direta. As classes ElementoQuimico e ElementoQuimicoFactory devem ficar no mesmo pacote para permitir 
que a fábrica tenha acesso ao construtor de ElementoQuimico. 
public class ElementoQuimicoFactory { 
private Map elementos; 
 
public ElementoQuimico criarElemento(String simbolo, String nome) { 
ElementoQuimico elemento = elementos.get(simbolo); 
if (elemento == null) { 
elemento = new ElementoQuimico(simbolo, nome); 
elementos.put(simbolo, elemento); 
} 
return elemento; 
} 
} 
 
public class ElementoQuimico { 
private String simbolo; 
private String nome; 
 
ElementoQuimico(String simbolo, String nome) { 
this.simbolo = simbolo; 
this.nome = nome; 
} 
// getters e setters omitidos 
} 
 
public class Exemplo { 
public static void main(String[] args) { 
ElementoQuimicoFactory factory = new ElementoQuimicoFactory(); 
SubstanciaSimples s1 = new SubstanciaSimples("Oxigênio", 
factory.criarElemento("O", "Oxigênio"), 2); 
SubstanciaSimples s2 = new SubstanciaSimples("Ozônio", 
factory.criarElemento("O", "Oxigênio"), 3); 
 
Map composicaoAgua = new HashMap(); 
composicaoAgua.put(factory.criarElemento("H", "Hidrogenio"), 2); 
composicaoAgua.put(factory.criarElemento("O", "Oxigênio"), 1); 
 
SubstanciaComposta s3 = new SubstanciaComposta("Agua", composicaoAgua); 
} 
} 
Consequências e padrões relacionados ao Flyweight 
O padrão Flyweight permite a utilização mais racional da memória em casos nos quais exista a 
criação de um número elevado de pequenos objetos replicados. Ele é aplicável, especialmente, quando 
esses objetos possuírem um estado intrínseco imutável. 
Um exemplo conhecido de objetos imutáveis em Java corresponde aos objetos da classe String. 
As constantes literais do tipo String em Java são implementadas como objetos String imutáveis, 
compartilhados em um pool de constantes. Desse modo, apesar de uma constante literal String ser utilizada 
em vários módulos de um programa, apenas uma instância dessa constante é criada. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 57 
 
Comentário 
O padrão Flyweight pode ser combinado com o padrão Composite na implementação de estruturas de árvores 
com folhas compartilhadas. Outro padrão frequentemente combinado com o Flyweight é o State. 
No padrão State, cada estado de um objeto é representado como uma classe. Desse modo, se uma 
classe Pedido, por exemplo, tiver cinco estados possíveis e instanciarmos mil pedidos, teremos para cada 
pedido um objeto estado associado. Portanto, se essa estrutura for implementada, instanciando-se 
diretamente cada objeto, teremos dois mil objetos em memória. Entretanto, se o padrão Flyweight for 
utilizado, teremos apenas mil objetos da classe Pedido mais cinco objetos (um para cada estado possível) 
compartilhados por esses mil objetos. 
Conseguiu perceber como o uso da memória fica mais racional nesse caso? 
Intenção do padrão Proxy 
O propósito do padrão Proxy é fornecer aos clientes um objeto proxy, com a mesma interface do 
objeto destino real, que delega as requisições dos clientes para o objeto real. 
Esse padrão é muito utilizado quando o objeto real é remoto ou de instanciação muito custosa, como, 
por exemplo, o caso de imagens, ou quando se deseja interceptar a requisição antes que ela chegue ao 
objeto real para fins de autorização, monitoração, validação, entre outros. 
Problema resolvido pelo padrão Proxy 
Quando estamos desenvolvendo um programa orientado a objetos, implementar a chamada de uma 
operação de outro objeto é uma tarefa simples caso eles estejam no mesmo processo. Imagine, agora, que 
esses objetos estejam em processos diversos, rodando em máquinas diferentes, como é comum em projetos 
distribuídos. 
Para que um objeto consiga chamar uma operação de um objeto remoto, é necessário estabelecer 
uma conexão, empacotar os dados para transmissão e fazer a chamada, ou seja, não é simplesmente 
chamar uma operação como ocorre em um programa orientado a objetos simples. 
Comentário 
Para você entender melhor o problema, veja o código a seguir, que implementa a interação de um módulo 
cliente com um módulo EJB servidor que executa uma transferência entre contas. Imagine que o módulo cliente seja 
uma parte da implementação da camada de interface com o usuário que precisa solicitar esse serviço ao componente 
de negócio remoto. Se você não conhecer a tecnologia EJB, não tem problema. 
O importante é você perceber a complexidade que foi inserida na implementação para que o módulo 
cliente conseguisse fazer a chamada da operação do componente EJB correspondente à lógica de negócio. 
public class ModuloCliente { 
private ServicoConta session; // referência para o objeto remoto 
private static final Class homeClazz = ServicoContaHome.class; 
 
public void executarTransferencia(Transferencia transf) throws ServicoContaException 
{ 
try { 
ServicoContaHome home = (ServicoContaHome) getHome("ServicoConta", 
homeClazz); 
session = home.create(); 
session.executarTransferencia(transf); // chamada delegada ao objeto 
real 
} catch (Exception ex) { 
throw new ServicoContaException(ex); 
} 
} 
public EJBHome getHome(String name, Class clazz) throws NamingException { 
Object objref = context.lookup(name); 
Padrões de Projetos de Software com Java 
Marcio Quirino - 58 
 
EJBHome home = (EJBHome) PortableRemoteObject.narrow(objref, clazz); 
return home; 
} 
} 
Imagine, agora, que o sistema que você está desenvolvendo tenha dezenas de módulos clientes, 
que precisam interagir com os objetos EJB que realizam a lógica de negócio. Indo além, suponha que você 
precise mudar a tecnologia de invocação dos serviços de EJB para uma REST API. 
Qual seria o impacto nos módulos clientes? 
Se pensou em um impacto gigantesco, você acertou! 
Embora oComposite ................................................................................................................ 48 
Problema resolvido pelo padrão Composite ......................................................................................... 48 
Solução do padrão Composite ............................................................................................................. 49 
Consequências e padrões relacionados ao Composite ....................................................................... 50 
Intenção do padrão Facade ..................................................................................................................... 51 
Problema resolvido pelo padrão Facade .............................................................................................. 51 
Solução do padrão Facade .................................................................................................................. 52 
Consequências e padrões relacionados ao Facade ............................................................................. 53 
4. Padrões de projeto Flyweight e Proxy ................................................................................... 53 
Intenção do padrão Flyweight .................................................................................................................. 53 
Solução do padrão Flyweight ............................................................................................................... 54 
Consequências e padrões relacionados ao Flyweight ......................................................................... 56 
Intenção do padrão Proxy ........................................................................................................................ 57 
Problema resolvido pelo padrão Proxy ................................................................................................. 57 
Solução do padrão Proxy ......................................................................................................................... 58 
Consequências e padrões relacionados ao Proxy ............................................................................... 60 
5. Conclusão............................................................................................................................... 60 
Considerações Finais ............................................................................................................................... 60 
Referências .............................................................................................................................................. 61 
Explore+ ................................................................................................................................................... 61 
Padrões GoF Comportamentais .......................................................................................................... 62 
Apresentação ........................................................................................................................................... 62 
Propósito .................................................................................................................................................. 62 
Preparação ............................................................................................................................................... 62 
Padrões de Projetos de Software com Java 
Marcio Quirino - 3 
 
1. Padrões de projeto comportamentais Chain of Responsibility, Command e Iterator ........... 62 
Padrão Chain of Responsibility ................................................................................................................ 62 
Intenção do padrão Chain of Responsibility ......................................................................................... 62 
Problema resolvido pelo padrão Chain of Responsibility ..................................................................... 62 
Solução do padrão Chain of Responsibility .......................................................................................... 63 
Consequências e padrões relacionados ao padrão Chain of Responsibility ........................................ 66 
Padrão Command .................................................................................................................................... 67 
Intenção do padrão Command ............................................................................................................. 67 
Problema resolvido pelo padrão Command ......................................................................................... 67 
Solução do padrão Command .............................................................................................................. 68 
Consequências e padrões relacionados ao padrão Command ............................................................ 70 
Padrão Iterator ......................................................................................................................................... 70 
Intenção do padrão Iterator .................................................................................................................. 70 
Problema resolvido pelo padrão Iterator............................................................................................... 70 
Solução do padrão Iterator ................................................................................................................... 71 
Consequências e padrões relacionados ao padrão Iterator ................................................................. 72 
2. Padrões de projeto comportamentais Mediator, Memento e Strategy ................................. 73 
Padrão Mediator ....................................................................................................................................... 73 
Intenção do padrão Mediator ................................................................................................................ 73 
Problema resolvido pelo padrão Mediator ............................................................................................ 73 
Solução do padrão Mediator ................................................................................................................ 73 
Consequências e padrões relacionados ao padrão Mediator ............................................................... 75 
Padrão Memento ...................................................................................................................................... 76 
Intenção do padrão Memento ............................................................................................................... 76 
Problema resolvido pelo padrão Memento ........................................................................................... 76 
Solução do padrão Memento ............................................................................................................... 77 
Consequências e padrões relacionados ao padrão Memento .............................................................. 78 
Padrão Strategy ....................................................................................................................................... 79 
Intenção do padrão Strategy ................................................................................................................ 79 
Problema resolvido pelo padrão Strategy............................................................................................. 79 
Solução do padrão Strategy ................................................................................................................. 80 
Consequências e padrões relacionados ao padrão Strategy ............................................................... 81 
3. Padrões de projeto comportamentais Observer, Visitor e State ...........................................próprio EJB já contenha uma implementação do padrão Proxy, oferecendo um objeto proxy 
local que abstrai os detalhes de transporte e comunicação via rede com o objeto EJB remoto, o exemplo 
anterior mostra que os módulos clientes continuam tendo que lidar com detalhes da tecnologia envolvida na 
comunicação entre as camadas de uma aplicação. 
Solução do padrão Proxy 
A estrutura da solução proposta pelo padrão Proxy está representada no diagrama de classes a 
seguir. 
 
A dinâmica entre os participantes é ilustrada no diagrama de sequência a seguir. Note que o papel 
do proxy é fundamentalmente repassar a chamada ao objeto destino. 
 
O principal participante do padrão é o elemento Proxy que controla o acesso ao objeto destino. 
Existem diferentes tipos de proxy: 
Padrões de Projetos de Software com Java 
Marcio Quirino - 59 
 
A. Remote proxy 
✓ É responsável por oferecer uma interface local idêntica à implementada pelo componente 
remoto de destino (RealSubject), isolando o cliente das questões envolvendo 
estabelecimento de conexão, codificação do conteúdo da requisição e tratamento de erros. 
Esse proxy é utilizado na comunicação entre objetos distribuídos em diferentes máquinas. 
B. Virtual proxy 
✓ É utilizado quando o objeto real tem uma carga muito custosa ou demorada. Uma interface 
com o usuário com imagens pesadas, por exemplo, pode fazer uso de virtual proxies para o 
conteúdo real das imagens, permitindo apresentar mensagens ou imagens mais leves e, 
somente sob o comando do usuário, trazer a imagem completa para a tela. Outro uso para 
esse tipo de proxy ocorre em frameworks de persistência de objetos, quando um objeto está 
relacionado a centenas de outros e desejamos acessar apenas as propriedades básicas do 
objeto, sem a necessidade de carregar todos os objetos relacionados, estratégia conhecida 
como lazy instantiation. 
C. Interceptor proxy 
✓ É utilizado quando a chamada para o objeto destino deve ser interceptada para realização de 
atividades como verificação de permissão de acesso, monitoração, redirecionamento da 
chamada, entre outras possibilidades, para, então, ser redirecionada ao objeto destino. 
O código a seguir mostra a implementação de um proxy para o componente de negócio 
implementado em EJB. Esse é um padrão J2EE conhecido como BusinessDelegate, implementado 
utilizando o padrão de projeto proxy. 
A classe ServicoContaProxyEJB implementa uma interface genérica (ServicoContaDelegate) que 
replica as operações oferecidas pelo objeto remoto destino. A implementação desse proxy encapsula o 
mecanismo de conexão com o componente de negócio, neste caso, EJB. Se implementarmos um 
mecanismo de acesso via REST API, bastaria criarmos outro proxy implementando a interface 
ServicoContaDelegate, delegando as chamadas para essa API. Perceba que a operação 
executarTransferencia delega a execução para o componente remoto, mas, para isso, o proxy precisa se 
conectar ao objeto EJB destino. 
public interface ServicoContaDelegate { 
void executarTransferencia(Transferencia tranf) throws ServicoContaException; 
} 
 
public class ServicoContaProxyEJB implements ServicoContaDelegate { 
private ServicoConta session; // referência para o objeto remoto 
private static final Class homeClazz = ServicoContaHome.class; 
 
public void executarTransferencia(Transferencia transf) throws ServicoContaException 
{ 
try { 
session = getSession(homeClazz); // conexão com o objeto remoto 
session.executarTransferencia(transf); // chamada delegada ao objeto 
real 
} catch (Exception ex) { 
throw new ServicoContaException(ex); 
} 
} 
public ServicoConta getSession(Class homeClazz) throws ServicoContaException { 
try { 
ServicoContaHome home = (ServicoContaHome) getHome("ServicoConta", 
homeClazz); 
session = home.create(); 
} catch (Exception ex) { 
throw new ServicoContaException(ex); 
} 
} 
 
public EJBHome getHome(String name, Class clazz) throws NamingException { 
Padrões de Projetos de Software com Java 
Marcio Quirino - 60 
 
Object objref = context.lookup(name); 
EJBHome home = (EJBHome) PortableRemoteObject.narrow(objref, clazz); 
return home; 
} 
} 
O código a seguir mostra como ficariam os módulos clientes que precisassem requisitar um serviço 
de componente de negócio. Note que a implementação desses clientes está totalmente isolada dos detalhes 
de conexão e tratamento de exceções inerentes ao protocolo de comunicação com o objeto remoto. 
public class ModuloCliente { 
private ServicoContaDelegate servicoConta; 
public ModuloCliente(ServicoContaDelegate servicoConta) { 
this.servicoConta = servicoConta; 
} 
 
public void executarTransferencia(Transferencia transf) { 
try { 
servicoConta.executarTransferencia(transf); 
} catch (ServicoContaException ex) { 
// apresenta mensagem de erro 
} 
} 
} 
Consequências e padrões relacionados ao Proxy 
O padrão Proxy introduz um nível de indireção para o acesso a um objeto. Esse nível adicional 
possibilita a transparência de localização do objeto destino para os objetos clientes, caso o objeto destino 
esteja em outra máquina. Além disso, é possível construir objetos grandes ou numerosos sob demanda sem 
mudar a interface oferecida ao módulo cliente. 
Comentário 
O padrão Proxy possui algumas similaridades com o padrão Adapter. Ambos oferecem uma forma de acesso 
indireta ao objeto destino, porém, o padrão Adapter traduz uma interface padrão em uma interface específica oferecida, 
tipicamente, por um componente de terceiros, enquanto o padrão proxy preserva a mesma interface oferecida pelo 
objeto alvo. 
O padrão Decorator possui uma implementação similar a um proxy, porém, tem um propósito diferente, por 
adicionar novas funcionalidades a um objeto, enquanto um proxy mantém as funcionalidades oferecidas pelo objeto 
destino. 
5. Conclusão 
Considerações Finais 
Neste conteúdo, vimos como os padrões de projeto GoF estruturais podem auxiliar na criação de 
soluções de projeto mais flexíveis e menos acopladas. 
O padrão Adapter é muito utilizado para isolar módulos clientes das diferentes implementações de 
um mesmo serviço oferecidas por terceiros. O desafio fundamental do padrão Adapter reside na definição 
de uma interface comum às diversas soluções. 
Os padrões Bridge e Adapter possuem uma estrutura de solução similar, porém, com objetivos 
distintos. Enquanto o padrão Adapter visa adaptar uma interface comum a diferentes implementações, 
tipicamente conhecidas a posteriori, o padrão Bridge promove uma separação a priori entre uma abstração 
e as possíveis implementações da sua representação interna, permitindo que ambas possam evoluir de 
forma independente. 
O padrão Decorator é uma forma mais flexível de adicionar novos comportamentos a classes já 
existentes, se comparada às soluções baseadas em herança. A sua estrutura de solução é baseada na 
Padrões de Projetos de Software com Java 
Marcio Quirino - 61 
 
composição de objetos que compartilham uma interface genérica comum, gerando estruturas similares a 
uma cebola ou a uma matrioska (você conhece aquela série de bonecas russas de tamanhos variados 
colocadas umas dentro das outras?). 
O padrão Composite oferece uma estrutura de composição recursiva que permite o tratamento de 
hierarquias de objetos que possuam operações que se apliquem tanto às folhas como aos agregados. O 
padrão Facade é muito utilizado na estruturação da arquitetura de sistemas, oferecendo uma interface de 
alto nível para cada subsistema ou componente macro da solução. O padrão Flyweight oferece uma forma 
de utilização de memória mais eficiente em casos nos quais precisamos instanciar uma grande quantidade 
de objetos imutáveis ou que possuam uma parte significativa do seu estado imutável. 
O padrão Proxy fornece um nível de indireção a um objeto, de forma parecida com o padrão 
Decorator, pois ambos possuem uma referência para um objeto para o qual as requisições sãodirecionadas. 
Entretanto, esses padrões possuem objetivos distintos. O Decorator se preocupa em adicionar 
funcionalidades ao objeto alvo, enquanto o Proxy oferece a mesma interface do objeto destino, porém, 
isolando o cliente de problemas como acesso remoto ou otimização de instanciação de objetos com 
utilização intensiva de memória. 
Referências 
GAMMA, E.; HELM, R.; JOHNSON, R.; VLISSIDES, J. Design patterns: elements of reusable object-
oriented software. 1. ed. Boston: Addison-Wesley, 1994. 
METSKER, S. J.; WAKE, W. C. Design patterns in Java. 1.ed. Boston: Addison-Wesley, 2006. 
Explore+ 
Para saber mais sobre a programação orientada a objetos, acesse o site da DevMedia e leia o artigo 
Utilização dos princípios SOLID na aplicação de padrões de projeto. 
O site Padrões de Projeto/Design patterns – Refactoring.Guru apresenta um conteúdo interativo e 
bastante completo de todos os padrões GoF, com exemplos de código em diversas linguagens de 
programação. 
Além dos padrões GoF tradicionais, outros voltados para o desenvolvimento de aplicações 
corporativas em Java EE podem ser encontrados no livro Java EE 8 Design Patterns and Best Practices, 
escrito por Rhuan Rocha e João Purificação. A obra aborda padrões para interface com o usuário, lógica do 
negócio, integração de sistemas, orientação a aspectos, programação reativa e microsserviços. 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 62 
 
Padrões GoF Comportamentais 
Apresentação 
O grupo de padrões GoF comportamentais engloba Chain of Responsibility, Command, Interpreter, 
Iterator, Mediator, Memento, Observer, State, Strategy, Template Method e Visitor. São padrões ligados a 
algoritmos e à atribuição de responsabilidades em projetos orientados a objetos. Buscaremos identificar 
oportunidades para aplicá-los. Precisamos compreender essa família de padrões e saber como utilizá-los, 
pois, caso contrário, nossas soluções podem se tornar pouco flexíveis e de difícil manutenção. 
Propósito 
Compreender os padrões ligados a algoritmos e à atribuição de responsabilidades em projetos 
orientados a objetos, bem como identificar oportunidades para a sua aplicação são habilidades importantes 
para um projetista de software, pois, sem elas, as soluções geradas podem ser pouco flexíveis e dificultar a 
evolução de sistemas de software em prazo e custo aceitáveis. 
Preparação 
Antes de iniciar o conteúdo, é recomendado instalar em seu computador um programa que lhe 
permita elaborar modelos sob a forma de diagramas da UML (Linguagem Unificada de Modelagem). Nossa 
sugestão inicial é o Free Student License for Astah UML, usado nos exemplos deste estudo. Os arquivos 
Astah com diagramas UML utilizados neste conteúdo estão disponíveis para download. 
Recomendamos, também, a instalação de um ambiente de programação em Java, como o Apache 
Netbeans. Porém, antes de instalar o Netbeans, é necessário ter instalado o JDK (Java Development Kit) 
referente à edição Java SE (Standard Edition), que pode ser encontrado no site da Oracle Technology 
Network: Java SE - Downloads | Oracle Technology Network | Oracle. 
1. Padrões de projeto comportamentais Chain of 
Responsibility, Command e Iterator 
Padrão Chain of Responsibility 
Não são raras as situações em que uma requisição precisa passar por diferentes tratamentos, 
obedecendo a uma sequência específica, como em uma linha de produção. Por exemplo, ao tratar uma 
requisição no protocolo HTTP, pode ser necessário decriptar, descompactar e definir a codificação, antes 
que a informação possa ser tratada. E uma boa estratégia seria definir uma sequência de agentes para as 
transformações, cada um recebendo a requisição, executando sua parte, e repassando para o próximo 
agente. Esse é o modelo adotado no Chain of Responsibility, e aqui veremos como ele pode ser útil em 
nossos projetos. 
Intenção do padrão Chain of Responsibility 
Chain of Responsibility é um padrão que permite o envio de uma requisição para o primeiro elemento 
de uma cadeia de objetos que podem realizar algum processamento relacionado a essa requisição, fazendo 
com que o objeto requisitante não precise ficar acoplado a todos os objetos da cadeia, mas apenas ao objeto 
para o qual ele envia a requisição. 
Problema resolvido pelo padrão Chain of Responsibility 
Imagine que você esteja implementando um módulo tarifador de ligações telefônicas, responsável 
por calcular o custo de cada ligação telefônica efetuada em uma central telefônica do tipo PABX de uma 
empresa. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 63 
 
Suponha que exista uma regra de precificação específica para cada tipo de ligação: ligação interna 
entre ramais, ligação local, ligação DDD e ligação DDI. Uma solução possível para esse problema seria 
concentrar toda a lógica de tarifação em uma única classe, conforme podemos ver no código a seguir. 
public class ServicoTarifacaoTelefonica { 
 // método que calcula o custo de uma ligação recebida como parâmetro 
 public BigDecimal tarifar(Ligacao ligacao) { 
 // de acordo com o tipo da ligação, chama o método apropriado de cálculo 
 if (ligacao.isInterna()) { 
 return tarifarLigacaoInterna (ligacao); 
 } else if (ligacao.isLocal()) { 
 return tarifarLigacaoLocal (ligacao); 
 } else if (ligacao.isDDD()) { 
 return tarifarLigacaoDDD (ligacao); 
 } else if (ligacao.isDDI()) { 
 return tarifarLigacaoDDI (ligacao); 
 } else { 
 return new BigDecimal(0); 
 } 
 } 
 private BigDecimal tarifarLigacaoInterna (Ligacao ligacao) { 
 // código para tarifação de ligação interna 
 } 
 private BigDecimal tarifarLigacaoLocal (Ligacao ligacao) { 
 // código para tarifação de ligação local 
 } 
 private BigDecimal tarifarLigacaoDDD (Ligacao ligacao) { 
 // código para tarifação de ligação DDD 
 } 
 private BigDecimal tarifarLigacaoDDI (Ligacao ligacao) { 
 // código para tarifação de ligação DDI 
 } 
} 
Essa solução concentra toda a lógica de tarifação em um único módulo, tornando-o extenso e 
complexo. Imagine como ficaria esse módulo, caso outros tipos de ligação fossem adicionados, como, por 
exemplo, ligações para celular ou ligações considerando as operadoras de origem e destino. 
Solução do padrão Chain of Responsibility 
Suponha que você esteja em uma grande loja de departamentos querendo comprar uma televisão. 
O vendedor apresenta as condições para a compra, e você pede um desconto. Se o desconto estiver dentro 
do limite do vendedor, ele mesmo poderá aprová-lo. Caso contrário, o vendedor pedirá para você aguardar, 
pois ele terá que solicitar a autorização do supervisor. Se estiver no limite permitido para o supervisor, o 
desconto será aprovado, caso contrário, este terá que levar o pedido até o gerente, que poderá aprová-lo 
ou não. Ao final desse processo, o vendedor voltará para comunicar o resultado, ou seja, se o desconto foi 
autorizado. 
Perceba que você falou apenas com o vendedor, mas, a partir da sua requisição, uma cadeia de 
responsabilidade de aprovação de desconto foi acionada até a decisão final ser tomada. 
A solução proposta pelo padrão Chain of Responsibility é inspirada nessa abordagem da loja de 
departamentos. Em vez de um módulo concentrar a responsabilidade de conhecer todos os objetos ou 
métodos participantes da solução, o processamento é dividido em pequenos módulos que podem ser 
combinados de diferentes maneiras. A ideia é construir uma cadeia de objetos, na qual cada objeto pode 
fazer algum processamento com a requisição ou simplesmente repassá-la para o seu sucessor na cadeia. 
O diagrama de classes a seguir apresenta a estrutura da solução proposta pelo padrão. 
Padrões de Projetosde Software com Java 
Marcio Quirino - 64 
 
 
Diagrama de classes. 
O participante Client representa um módulo cliente responsável pelo envio de uma requisição, que 
será tratada por uma cadeia de objetos descendentes do participante abstrato Handler. Cada elemento da 
cadeia, representada pelos participantes do tipo ConcreteHandler, pode tratar a requisição ou repassá-la 
para o seu sucessor, definido pelo autorrelacionamento presente no participante Handler. Portanto, todo 
objeto ConcreteHandler tem um sucessor, que é uma instância de uma classe descendente de Handler. 
Os objetos ConcreteHandler formam a cadeia ilustrada no diagrama de sequência a seguir, em que 
a chamada do objeto Client à operação HandleRequest do primeiro ConcreteHandler pode ser encaminhada 
para o seu sucessor na cadeia, por meio de uma chamada à operação de mesmo nome. 
 
Diagrama de sequência. 
Você se lembra como funciona o tratamento de exceções em Java? As chamadas de métodos em 
um instante de execução de um programa formam uma cadeia, também conhecida por pilha de execução. 
Quando um bloco catch em um método captura uma exceção, ele pode: 
A. Fazer um tratamento local da exceção e encerrar o fluxo de exceção. 
B. Fazer um tratamento local da exceção e repassá-la para o método chamador ou bloco try-catch 
mais externo. 
C. Somente repassar a exceção para o método chamador ou bloco try-catch mais externo, sem fazer 
nenhum tratamento local. 
A cadeia de objetos que forma a estrutura de solução desse padrão funciona de forma similar ao 
tratamento de exceções em Java, pois cada Handler pode fazer um processamento local da requisição e/ou 
repassá-la para o seu sucessor. 
Agora, vamos reestruturar a solução apresentada anteriormente para o problema da tarifação de 
ligações, aplicando o padrão Chain of Responsibility. Primeiro, vamos definir a classe abstrata 
Padrões de Projetos de Software com Java 
Marcio Quirino - 65 
 
TarifadorLigacao correspondente ao participante Handler do padrão. Ela possui um atributo correspondente 
ao seu sucessor e à operação tarifar, que repassa a chamada para o objeto sucessor. Note que a operação 
tarifar corresponde à operação HandleRequest definida na estrutura do padrão. 
public abstract class TarifadorLigacao { 
 private TarifadorLigacao sucessor; 
 public TarifadorLigacao setSucessor(TarifadorLigacao sucessor) { 
 this.sucessor = sucessor; 
return this; // retorna ele próprio para facilitar a construção da cadeia de 
objetos 
 } 
 public BigDecimal tarifar (Ligacao ligacao) { 
 // este método na superclasse repassa a chamada para o próximo objeto da cadeia 
 return (sucessor != null) 
 ? sucessor.tarifar (ligacao); 
 : new BigDecimal(0); 
 } 
 } 
Em seguida, vamos implementar uma classe tarifador para cada tipo de ligação. Cada tarifador 
específico corresponde ao participante ConcreteHandler definido no padrão. A implementação do método 
tarifar em cada tarifador específico retorna o custo da ligação, se ela for do tipo apropriado ou, caso contrário, 
chama o método tarifar definido na superclasse que, por sua vez, repassa a responsabilidade do cálculo 
para o próximo tarifador da cadeia. 
public class TarifadorLigacaoInterna extends TarifadorLigacao { 
 // método que calcula o custo de uma ligação recebida como parâmetro 
 public BigDecimal tarifar(Ligacao ligacao) { 
 if (ligaçao.isInterna()) { 
 return tarifarLigacaoInterna(ligacao); // se for ligação 
interna, tarifa aqui 
 } else { 
 return super.tarifar(ligacao); // se não for, repassa 
para próximo da cadeia 
 } 
 } 
 private BigDecimal tarifarLigacaoInterna(Ligacao ligacao) { 
 // código para tarifação de ligação interna 
 } 
 } 
 
 public class TarifadorLigacaoLocal extends TarifadorLigacao { 
 // método que calcula o custo de uma ligação recebida como parâmetro 
 public BigDecimal tarifar (Ligacao ligacao) { 
 if (ligaçao.isLocal()) { 
 return tarifarLigacaoLocal(ligacao); // se ligação for 
local, tarifa aqui 
 } else { 
 return super.tarifar (ligacao); // se não for, passa para 
o próximo da cadeia 
 } 
 } 
 private BigDecimal tarifarLigacaoLocal (Ligacao ligacao) { 
 // código para tarifação de ligação local 
 } 
 } 
Em seguida, vamos implementar uma classe tarifador para cada tipo de ligação. Cada tarifador 
específico corresponde ao participante ConcreteHandler definido no padrão. A implementação do método 
tarifar em cada tarifador específico retorna o custo da ligação, se ela for do tipo apropriado ou, caso contrário, 
chama o método tarifar definido na superclasse que, por sua vez, repassa a responsabilidade do cálculo 
para o próximo tarifador da cadeia. 
public class TarifadorLigacaoInterna extends TarifadorLigacao { 
 // método que calcula o custo de uma ligação recebida como parâmetro 
 public BigDecimal tarifar(Ligacao ligacao) { 
Padrões de Projetos de Software com Java 
Marcio Quirino - 66 
 
 if (ligaçao.isInterna()) { 
 return tarifarLigacaoInterna(ligacao); // se for ligação interna, 
tarifa aqui 
 } else { 
 return super.tarifar(ligacao); // se não for, repassa para próximo da 
cadeia 
 } 
 } 
 private BigDecimal tarifarLigacaoInterna(Ligacao ligacao) { 
 // código para tarifação de ligação interna 
 } 
 } 
 
 public class TarifadorLigacaoLocal extends TarifadorLigacao { 
 // método que calcula o custo de uma ligação recebida como parâmetro 
 public BigDecimal tarifar (Ligacao ligacao) { 
 if (ligaçao.isLocal()) { 
 return tarifarLigacaoLocal(ligacao); // se ligação for local, tarifa 
aqui 
 } else { 
 return super.tarifar (ligacao); // se não for, passa para o próximo da 
cadeia 
 } 
 } 
 private BigDecimal tarifarLigacaoLocal (Ligacao ligacao) { 
 // código para tarifação de ligação local 
 } 
 } 
Finalmente, observe como na nova implementação a classe ServicoTarifacaoTelefonica pode operar 
com qualquer tarifador que seja plugado no seu construtor. A operação tarifar dessa classe simplesmente 
faz a requisição para o primeiro tarifador da cadeia. 
A classe ConfiguradorServicoTarifacaoTelefonica é um exemplo simples de como uma cadeia de 
responsabilidades pode ser criada e injetada em uma classe cliente. Ela cria uma cadeia de tarifadores, um 
para cada tipo de ligação, usando a operação setSucessor para ligar um tarifador ao outro. Uma vez criada, 
a cadeia é passada como argumento para o construtor da classe ServicoTarifacaoTefefonica. 
public class ServicoTarifacaoTelefonica() { 
 private TarifadorLigacao tarifador; 
 public ServicoTarifacaoTelefonica (TarifadorLigacao tarifador) { 
 this.tarifador = tarifador; 
 } 
 public BigDecimal tarifar (LigacaoTelefonica ligacao) { 
 return tarifador.tarifar(ligacao); // chama o primeiro tarifador da cadeia 
 } 
 } 
 
 public class ConfiguradorServicoTarifacaoTelefonica { 
 public ServicoTarifacaoTelefonica getInstance() { 
 // cria a cadeia de tarifadores 
 TarifadorLigacao cadeiaTarifadores = new TarifadorLigacaoInterna(). 
 setSucessor(new TarifadorLigacaoLocal()). 
 setSucessor(new TarifadorLigacaoDDD()). 
 setSucessor(new TarifadorLigacaoDDI()); 
 // instancia o serviço passando a cadeia de tarifadores 
 return new ServicoTarifacaoTelefonica(cadeiaTarifadores);} 
 } 
Consequências e padrões relacionados ao padrão Chain of Responsibility 
O padrão Chain of Responsibility reduz a complexidade de uma classe que tenha que lidar com várias 
possibilidades de tratamento de uma requisição, transformando as diversas operações e estruturas 
condicionais complexas originalmente existentes em um conjunto de objetos interconectados, que podem 
ser combinados de diferentes formas, gerando uma solução menos acoplada e mais flexível. Por outro lado, 
Padrões de Projetos de Software com Java 
Marcio Quirino - 67 
 
existe o risco de uma requisição não ser respondida de forma adequada, caso a configuração da cadeia não 
seja corretamente realizada. 
Atenção 
Esse padrão é frequentemente utilizado em conjunto com o padrão Composite. Nesse caso, não é necessário 
implementar um sucessor, dado que podemos utilizar o relacionamento entre o agregado e as suas partes para 
encadear as chamadas pelos elementos da estrutura de composição. 
Padrão Command 
Imagine que você precisa criar uma simples calculadora para números inteiros, com as operações 
fundamentais, ou seja, soma, subtração, multiplicação e divisão inteira, algo que seria resolvido com uma 
estrutura de seleção. Porém, gostaram muito da sua calculadora, e pediram novas operações como 
exponenciação e módulo. Não seria mais interessante definir um modelo abstrato, com entrada de dois 
inteiros, execução e retorno do resultado inteiro, e derivar classes concretas para cada operação? O padrão 
Command visa esse tipo de modelagem, encapsulando os algoritmos em uma família de classes, o que 
facilita muito a evolução de nossos sistemas. 
Intenção do padrão Command 
Command é um padrão que encapsula uma requisição em um objeto. Em projetos que não utilizam 
esse padrão, uma requisição é normalmente realizada por meio de uma simples chamada de operação. O 
encapsulamento de requisições em objetos desacopla o requisitante e o objeto executor, o que possibilita: 
1. Parametrizar as requisições disparadas pelos clientes; 
2. Criar filas de requisições; 
3. Registrar o histórico de requisições; 
4. Implementar operações para desfazer (undo) ou refazer (redo) o processamento realizado 
para atender uma requisição. 
Problema resolvido pelo padrão Command 
Imagine que você esteja desenvolvendo um jogo no qual as ações associadas às teclas ou aos 
botões do mouse possam ser configuradas. Veja no quadro a seguir um exemplo de configuração. 
Evento Ação 
Tecla W ir para frente 
Tecla S Ir para trás 
Tecla espaço Pular 
Botão 4 do mouse Pular 
Quadro: Configuração de teclas. Elaborado por: Alexandre Luis Correa. 
O módulo de configuração do jogo deve permitir que o usuário defina a correspondência desejada 
entre os eventos e as respectivas ações. Poderíamos resolver esse problema definindo constantes 
equivalentes a todos os eventos e ações do programa e, a partir dessas constantes, estabelecer uma 
configuração, correlacionando os tipos de eventos com os códigos das ações. 
O código a seguir apresenta o esqueleto dessa solução, em que a operação tratarEvento é executada 
sempre que um evento de interface ocorrer. Essa operação obtém o código da ação associada ao evento e 
invoca a operação correspondente do jogo. 
public class InterfaceJogo { 
 private Jogo jogo; 
 // mapa de configuração evento -> ação do jogo 
 Map configuracao = new HashMap(); 
 
 public InterfaceJogo (Jogo jogo) { 
 this.jogo = jogo; 
 } 
 public void setConfiguracaoJogo(Map configuracao) { 
Padrões de Projetos de Software com Java 
Marcio Quirino - 68 
 
 this.configuracao = configuracao; 
 } 
 public void tratarEvento (int evento) { 
 // acessa a tabela para obter o código da ação correspondente ao evento 
 int codigoAcao = getCodigoAcao(evento); 
 // executa a ação correspondente ao código 
 switch (codigoAcao) { 
 case IR_PARA_FRENTE: jogo.irParaFrente(); 
 break; 
 case IR_PARA_TRAS: jogo.irParaTras(); 
 break; 
 case PULAR: jogo.pular(); 
 break; 
 } 
 } 
 private int getCodigoAcao (int evento) { 
 return configuracao.get(evento); 
 } 
 } 
Essa solução cria um acoplamento entre o elemento que captura o evento disparado pela interface 
com o usuário (InterfaceJogo) e o módulo que realiza as operações em resposta ao Jogo, além de conter 
uma estrutura condicional não extensível. Imagine que o jogo tenha cinquenta comandos. 
Consegue visualizar como essa estrutura condicional definida pelo comando switch ficaria enorme? 
Solução do padrão Command 
A solução proposta pelo padrão Command consiste em transformar cada ação em um objeto. 
Portanto, em vez de as ações serem implementadas como operações de uma classe, cada ação é 
implementada individualmente em uma classe. O diagrama a seguir apresenta a estrutura do padrão. 
 
Diagrama de classes. 
Cada ação é definida em uma classe correspondente ao participante ConcreteCommand, que 
implementa a interface abstrata Command, na qual a operação genérica execute está definida. O 
participante Client corresponde a um módulo cliente responsável por fazer a instanciação e a associação de 
cada comando ao respectivo elemento Receiver, módulo que efetivamente realizará as ações associadas à 
requisição. O participante Invoker corresponde ao elemento que dispara o comando, como um botão em 
uma interface gráfica, por exemplo. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 69 
 
Veja, a seguir, a estrutura do código para a configuração dos eventos do jogo com a aplicação desse 
padrão. Primeiro, criamos a interface genérica Comando e os comandos concretos correspondentes às 
ações do jogo. 
public interface Comando { 
 public abstract void executar(); 
 } 
 
 public class Comando_IrParaFrente implements Comando { 
 private Jogo jogo; 
 public Comando_IrParaFrente (Jogo jogo) { 
 this.jogo = jogo; 
 } 
 public void executar() { 
 jogo.irParaFrente(); 
 } 
 } 
 public class Comando_IrParaTras implements Comando { 
 private Jogo jogo; 
 public Comando_IrParaTras (Jogo jogo) { 
 this.jogo = jogo; 
 } 
 public void executar() { 
 jogo.irParaTras(); 
 } 
 } 
Note que o construtor de cada comando concreto recebe o objeto da classe Jogo para o qual a 
requisição será passada. Portanto, Jogo corresponde ao participante Receiver do padrão. 
Em seguida, vamos criar a configuração do jogo, associando os eventos gerados pela interface com 
o usuário, isto é, teclas e botões do mouse, com os comandos. Note que podemos associar o mesmo 
comando a diferentes eventos de interface, como ocorre com o comando pular. A classe ConfiguracaoJogo 
corresponde ao participante Client do padrão. 
public class ConfiguracaoJogo { 
 public static Map obterConfiguracaoPadrao (Jogo jogo) { 
 Comando comando_ParaFrente = new Comando_IrParaFrente(jogo); 
 Comando comando_ParaTras = new Comando_IrParaTras(jogo); 
 Comando comando_Pular = new Comando_Pular(jogo); 
 
 Map comandoMap = new HashMap(); 
 comandoMap.put(TECLA_W, comando_ParaFrente); 
 comandoMap.put(TECLA_S, comando_ParaFrente); 
 comandoMap.put(TECLA_ESPACO, comando_Pular); 
 comandoMap.put(BOTAO_4, comando_Pular); 
 
 return comandoMap; 
 } 
 } 
Por fim, vamos ver a nova implementação da classe InterfaceJogo correspondente ao participante 
Invoker do padrão. 
Essa classe recebeuma configuração dos códigos dos eventos de interface e seus respectivos 
comandos. Note como, em comparação com a versão original, o método tratarEvento ficou desacoplado de 
todos os possíveis comandos. Isso permite adicionarmos comandos ao jogo sem que esta classe precise 
ser modificada. 
public class InterfaceJogo { 
 private Map configuracao; 
 public void setConfiguracaoJogo(Map configuracao) { 
 this.configuracao = configuracao; 
 } 
 public void tratarEvento(int evento) { 
Padrões de Projetos de Software com Java 
Marcio Quirino - 70 
 
 // acessa a tabela para obter o comando correspondente ao evento e o 
executa. 
 getComando(evento).execute(); 
 } 
 private Comando getComando (int evento) { 
 return configuracao.get(evento); 
 } 
 } 
 
 public class AppExemplo { 
 public static void main (String[] args) { 
 Jogo jogo = new Jogo(); 
 InterfaceJogo controlador = new InterfaceJogo(); 
 
controlador.setConfiguracaoJogo(ConfiguracaoJogo.obterConfiguracaoPadrao(jogo)); 
 // ... código restante para colocar o jogo no ar 
 } 
 } 
Consequências e padrões relacionados ao padrão Command 
O padrão Command promove o desacoplamento entre o objeto que faz a requisição e o objeto que 
realiza a operação requisitada. Cada comando passa a ser uma classe e, com isso, é possível reutilizar um 
código comum entre os comandos e adicionar comandos sem precisar alterar classes já existentes. 
Esse desacoplamento também resulta em maior facilidade para a implementação de testes unitários 
automatizados, uma vez que podemos executar uma sequência de comandos de forma isolada do seu 
mecanismo de disparo, como os eventos da interface gráfica com o usuário, por exemplo. A interface 
Comando pode ser estendida adicionando-se as seguintes operações: 
A. undo 
✓ Responsável por desfazer as ações realizadas pelo comando. 
B. redo 
✓ Responsável por realizar novamente as ações desfeitas pela operação undo. 
Como o comando é encapsulado em um objeto, ele pode armazenar as informações necessárias 
para reverter ou repetir suas ações, tornando possível a implementação dessas operações. 
Reflexão 
Como um macro comando, isto é, uma sequência de comandos, pode ser implementado, aplicando-se 
simultaneamente os padrões Command e Composite? 
Padrão Iterator 
Ao trabalhar no paradigma estruturado, é comum o uso de vetores para representar um conjunto de 
valores do mesmo tipo, mas quando usamos a orientação a objetos, é mais indicado organizar conjuntos de 
objetos por meio de coleções. Ao contrário dos vetores, que apresentam tamanho fixo, permitindo um acesso 
indexado, as coleções trabalham com o acréscimo dinâmico de elementos, exigindo um método próprio para 
acesso, e nesse ponto o padrão Iterator assume um papel muito importante. Aqui veremos como utilizar 
esse padrão para efetuar tarefas comuns sobre as coleções como, por exemplo, exibir os dados de um 
conjunto de entidades no terminal. 
Intenção do padrão Iterator 
O objetivo do padrão Iterator é permitir o acesso sequencial aos objetos de uma coleção, sem expor 
a sua representação interna. 
Problema resolvido pelo padrão Iterator 
Suponha que você tenha que percorrer uma coleção de produtos armazenados em um ArrayList. 
Você poderia escrever um código como o apresentado a seguir: 
Padrões de Projetos de Software com Java 
Marcio Quirino - 71 
 
public class Exemplo { 
 public void imprimirProdutos(ArrayList produtos) { 
 for (Produto produto : produtos) 
 System.out.println (produto.getNome()); 
 } 
 } 
O problema dessa solução é que a classe Exemplo conhece a representação interna da coleção de 
produtos, isto é, ela sabe que os produtos estão organizados em um ArrayList. 
Atenção 
No exemplo apresentado, caso tivéssemos outra coleção de produtos organizada em uma estrutura de mapa 
indexado pelo código do produto, não poderíamos utilizar a classe Exemplo, pois ela só funciona com produtos 
organizados em um ArrayList. 
Solução do padrão Iterator 
A solução proposta pelo padrão Iterator consiste em colocar a responsabilidade pelo percurso e pelo 
acesso aos elementos de uma coleção em um objeto específico, denominado Iterator. A estrutura do padrão 
está ilustrada no diagrama de classes a seguir. 
 
O participante Aggregate representa uma coleção genérica cujos elementos podem ser percorridos 
sequencialmente pelo conjunto de operações (First, Next, IsDone e CurrentItem) definido pelo participante 
Iterator. O participante ConcreteAggregate representa uma coleção específica, que é responsável por criar 
elementos do tipo ConcreteIterator capazes de percorrê-la. 
O framework de estrutura de dados da linguagem Java implementa esse padrão. Coleções como 
ArrayList, LinkedList, HashSet e TreeSet são descendentes da classe genérica Collection. Nesse caso, as 
coleções específicas correspondem ao participante ConcreteAggregate, enquanto a classe Collection 
corresponde ao participante Aggregate. Em Java, a interface genérica Iterator define um conjunto de 
operações um pouco diferente daquele definido na estrutura do padrão: 
A. hasNext 
✓ Verifica se existe um próximo elemento ou se o cursor já está posicionado no último elemento 
da coleção. 
B. next 
✓ Retorna o próximo elemento da coleção. Na primeira chamada, ele retorna o primeiro 
elemento da coleção. 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 72 
 
C. remove 
✓ Remove um elemento da coleção. 
Cada coleção define uma operação Iterator que retorna um objeto ConcreteIterator capaz de 
percorrê-la. O diagrama de classes a seguir ilustra os iteradores correspondentes às coleções ArrayList 
(ArrayIterator), LinkedList (ListIterator), HashSet (KeyIterator) e TreeSet (ValueIterator). 
 
Veja, a seguir, um exemplo de utilização desse padrão em Java. O método removerItensSemEstoque 
recebe uma coleção de produtos. Ele solicita um Iterator à coleção e utiliza as operações hasNext e next 
para percorrê-la, removendo os produtos cuja quantidade em estoque for zero. 
public void removerItensSemEstoque (Collection produtos) { 
 // coleção de produtos cria e retorna um iterator capaz de percorrer a coleção 
 Iterator iterator = produtos.iterator(); 
 // coleção de produtos é percorrida com as operações hasNext e next 
 while (iterator.hasNext()) { 
 Produto produto = iterator.next(); 
 if (Produto.getQuantidadeEstoque() == 0) { 
 itens.remove(item); 
 } 
 } 
 } 
Note que esse método funciona com qualquer coleção, isto é, ArrayList, LinkedList, HashSet ou 
TreeSet. 
Consequências e padrões relacionados ao padrão Iterator 
O principal benefício do padrão Iterator é permitir que os módulos clientes possam percorrer 
sequencialmente os elementos de uma coleção de forma independente da sua representação interna. Outro 
benefício é a possibilidade de haver diversas instâncias de Iterator ativas para uma mesma coleção, uma 
vez que cada Iterator guarda o seu próprio estado, isto é, a posição corrente na coleção. 
Além disso, é possível implementar iteradores com diferentes formas de percurso. Por exemplo, 
podemos percorrer uma árvore binária em pré-ordem, ordem simétrica e pós-ordem. Cada forma de percurso 
pode ser definida em uma implementação específica da interface Iterator. 
Atenção 
O padrão Iterator é frequentemente utilizado com o padrão Factory Method, uma vez que cada método Iterator 
do participante ConcreteAggregate é um Factory Method responsável por instanciar o respectivo ConcreteIterator. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 73 
 
2. Padrões de projeto comportamentais Mediator, Memento e 
StrategyPadrão Mediator 
Nem sempre uma negociação ocorre de forma simples, e quando a situação se torna mais crítica, 
um mediador independente pode ser a única solução. O mesmo ocorre em termos de softwares, quando 
diversos módulos precisam trabalhar em conjunto para a realização de tarefas específicas, mas de forma 
que não sejam feitas chamadas diretas entre eles, evitando aumentar o acoplamento. Por exemplo, um 
processo de compra na Web envolve a utilização de vários módulos, nos quais o carrinho precisa de 
informações do estoque e o pagamento demanda ações logísticas. Para impedir que esses módulos se 
comuniquem diretamente, as chamadas podem ser feitas via classe Mediator. 
Intenção do padrão Mediator 
O padrão Mediator encapsula a forma de interação entre um conjunto de objetos, com o objetivo de 
evitar que eles tenham que referenciar uns aos outros explicitamente. 
Problema resolvido pelo padrão Mediator 
Em um sistema de comércio eletrônico, quando o cliente efetua o pagamento, a compra deve ser 
confirmada, o processo de logística de entrega deve ser disparado e um e-mail de confirmação do pedido 
deve ser enviado para o cliente. 
Imagine que um desenvolvedor tenha dado a solução esquematizada no diagrama de sequência a 
seguir. 
 
Nessa solução, o módulo Pagamento, após realizar o pagamento da compra, chama a operação 
confirmar do módulo Compra, que, por sua vez, chama a operação prepararEntrega do módulo Logística e 
a operação enviarConfirmacaoCompra do módulo SAC, responsável por enviar um e-mail para o usuário 
com os dados da compra. 
Você consegue visualizar o alto acoplamento entre as classes na realização do processo de 
fechamento da compra? Imagine que você precise inserir uma etapa nesse processo, como a baixa no 
estoque, por exemplo. Você teria que adicionar uma dependência no módulo Compra ou no módulo 
Logística, que ficaria responsável por chamar uma operação do módulo Estoque. 
Como simplificar interações complexas entre os objetos, com o objetivo de reduzir o acoplamento 
entre eles e permitir a criação de novas interações sem que esses objetos precisem ser alterados? Essa é 
a essência do problema que o padrão Mediator visa solucionar. 
Solução do padrão Mediator 
A estrutura da solução proposta pelo padrão Mediator está representada no diagrama de classes a 
seguir: 
Padrões de Projetos de Software com Java 
Marcio Quirino - 74 
 
 
A solução consiste em centralizar as comunicações entre os objetos em um elemento denominado 
mediador. O participante Mediator define uma interface padrão que cada objeto utilizará para se comunicar 
com os demais envolvidos em uma interação. Esses objetos formam a hierarquia definida pelo participante 
Colleague. O participante ConcreteMediator, por sua vez, corresponde a um elemento concreto que mantém 
referências para todos os objetos cujas interações ele precisará mediar. 
Vamos modificar a solução do problema do comércio eletrônico aplicando o padrão Mediator. As 
classes Pagamento, Compra, Logistica e SAC correspondem ao participante ConcreteColleague e são 
especializações da classe genérica Colleague, na qual está definida uma referência ao objeto mediador. 
Note que a classe Pagamento notifica o mediador de que o pagamento foi concluído. Da mesma forma, a 
classe Compra notifica o mediador de que a compra foi encerrada. Portanto, esses objetos passam a se 
comunicar apenas com o mediador. Os módulos Pagamento e Conta apenas notificam o evento que ocorreu, 
ficando a cargo do mediador definir o encaminhamento que deve ser dado a cada evento. 
public abstract class Colleague { 
 // Todo objeto da interação possui uma referência para o mediador 
 protected MediadorCompra mediador; 
 
 public Colleague(MediadorCompra mediador) { 
 this.mediador = mediador; 
 } 
 } 
 
 public class Pagamento extends Colleague { 
 public Pagamento(MediadorCompra mediador) { 
 super(mediador); 
 } 
 public void realizarPagamento(int idCompra) { 
 // código para realizar pagamento 
 mediador.pagamentoConcluido(idCompra); // notifica o mediador 
 } 
 } 
 public class Compra extends Colleague { 
 public Compra(MediadorCompra mediador) { 
 super(mediador); 
 } 
 public void confirmar(int idCompra) { 
 // código para confirmação da compra 
 mediador.pedidoConfirmado(); // notifica o mediador 
 } 
 } 
 public class Logistica extends Colleague { 
 public Logistica(MediadorCompra mediador) { 
 super(mediador); 
 } 
 public void prepararEntrega(CompraVO dadosCompra) { 
 // código que inicia o processo de logística de entrega da compra 
 } 
Padrões de Projetos de Software com Java 
Marcio Quirino - 75 
 
 } 
 public class SAC extends Colleague { 
 public SAC(MediadorCompra mediador) { 
 super(mediador); 
 } 
 public void enviarEmailConfirmacaoCompra(CompraVO dadosCompra) { 
 // código para enviar o email de confirmação da compra para o cliente 
 } 
 } 
A implementação do mediador é ilustrada esquematicamente a seguir. A interface MediadorCompra 
define todas as notificações que os componentes participantes da interação podem enviar para o objeto 
mediador. Essa interface é implementada pela classe MediadorCompraSimples, que representa uma 
implementação simples das interações entre os objetos. Note que outras classes de mediação de compra 
podem ser implementadas, representando diferentes processos de interação entre os participantes. 
A classe MediadorCompraSimples corresponde ao participante ConcreteMediator. Ela possui uma 
referência para cada objeto participante das interações (pagamento, compra, logística e sac), recebe os 
eventos enviados por cada participante e dispara a execução de operações em resposta a cada evento. 
public interface MediadorCompra { 
 void pagamentoConcluido(int idCompra); 
 void pedidoConfirmado(); 
 } 
 
 public class MediadorCompraSimples implements MediadorCompra { 
 private Pagamento pagamento; 
 private Compra compra; 
 private Logistica logistica; 
 private SAC sac; 
 
 public MediadorCompraSimples(Pagamento pagamento, Compra compra, Logistica logistica, 
SAC sac) { 
 this.pagamento = pagamento; 
 this.compra = compra; 
 this.logistica = logistica; 
 this.sac = sac; 
 } 
 
 public void pagamentoConcluido(int idCompra) { 
 // mediador invoca operações da compra ao receber a notificação de pagamento. 
 compra.confirmar(idCompra); 
 } 
 
 public void pedidoConfirmado() { 
 // mediador invoca operações de logística e do sac ao receber a confirmação. 
 CompraVO dadosCompra = prepararDadosCompra(compra); 
 logistica.prepararEntrega(dadosCompra); 
 sac.enviarEmailConfirmacaoCompra(dadosCompra); 
 } 
 private CompraVO prepararDadosCompra(Compra compra) { 
 CompraVO dadosCompra = new CompraVO(); 
 // código para carga de todos os dados da compra estaria aqui 
 return dadosCompra; 
 } 
 } 
Consequências e padrões relacionados ao padrão Mediator 
O padrão Mediator gera uma solução de menor acoplamento para interações complexas entre 
objetos, pois ele substitui uma estrutura de interação do tipo muitos para muitos para uma estrutura um para 
muitos, que são mais fáceis de entender e manter. Por outro lado, o controle fica centralizado no mediador. 
Um cuidado especial deve ser tomado na implementação do mediador para evitar que ele se torne um 
componente monolítico com grande complexidade de manutenção. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 76 
 
O mediador deve ser apenas um concentrador de eventos e um coordenador de execução, ficando 
a lógica do processamento distribuída pelos elementos a ele conectados. 
Essepadrão é muito utilizado na implementação de componentes de interface com o usuário, em 
que um mediador centraliza a recepção e o tratamento dos eventos gerados pelos componentes da interface, 
como botões, campos de texto e listas de seleção, por exemplo. Em uma tela em que o usuário entra um 
endereço, a modificação do campo CEP pode acarretar a atualização dos campos logradouro, cidade e 
estado. Um mediador que receba a notificação de que o CEP foi alterado e promova a atualização dos 
demais campos envolvidos é uma solução normalmente empregada nesse contexto. 
Atenção 
Como a comunicação entre os participantes e o mediador se dá por meio de notificações de eventos, é comum 
o Mediator ser aplicado em conjunto com o padrão Observer. Nesse caso, o mediador corresponde ao participante 
Observer, enquanto os objetos notificadores correspondem ao participante Subject do padrão Observer. 
Padrão Memento 
Não são raras as situações em que precisamos desfazer algo, motivo pelo qual todos adoram a 
combinação de teclas CTRL e Z, presente em diversos aplicativos. O padrão Memento tem como objetivo 
viabilizar esse tipo de comportamento, empilhando instantâneos do estado do objeto, de forma a permitir a 
recuperação dos estados anteriores. Como um exemplo prático, supondo que você precise definir um 
aplicativo de modelagem tridimensional, o Memento permitiria ao profissional de modelagem desfazer suas 
últimas operações, uma funcionalidade essencial para dar maior liberdade e flexibilidade ao processo 
criativo. 
Intenção do padrão Memento 
Memento é um padrão que permite capturar o estado interno de um objeto, sem quebrar o seu 
encapsulamento, de forma que esse estado possa ser restaurado posteriormente. 
Problema resolvido pelo padrão Memento 
Suponha que você esteja desenvolvendo uma aplicação em que seja necessário desfazer o efeito 
produzido por uma ou mais operações em determinado objeto ou em um conjunto de objetos, procedimento 
conhecido como undo. Para isso, é necessário guardar o estado do objeto anterior à execução das 
operações que precisarão ser desfeitas. 
Veja a classe Pedido cujo código é listado a seguir. Ela define o número do pedido, o número do item 
a ser utilizado na criação de um novo item e a lista de itens do pedido. Apenas a classe Pedido tem acesso 
ao array de itens. Portanto, a adição de novos itens ao pedido deve ser realizada por meio da operação 
adicionarItem. 
public class Pedido { 
 private int numero; 
 private List itens; 
 private int numeroItemCorrente; 
 
 public Pedido(int numero) { 
 this.numero = numero; 
 itens = new ArrayList(); 
 numeroItemCorrente = 1; 
 } 
 
 public void adicionarItem(int qtde, String codigoProduto) { 
 itens.add (new ItemPedido (++numeroItemCorrente, qtde, codigoProduto)); 
 } 
 public int getNumero() { 
 return numero; 
 } 
 public void setNumero(int numero) { 
 this.numero = numero; 
Padrões de Projetos de Software com Java 
Marcio Quirino - 77 
 
 } 
 public Iterator getItens() { 
 return itens.iterator(); 
 } 
 } 
Agora pense como você implementaria uma funcionalidade que permitisse o usuário desfazer as 
últimas operações de adição de itens ao pedido. E se oferecêssemos um recurso que possibilitasse o usuário 
interromper sua compra e voltar mais tarde, sem perder o conteúdo já adicionado ao pedido? 
O padrão Memento serve para nos ajudar em situações como essas. 
Solução do padrão Memento 
A estrutura da solução proposta pelo padrão Memento está representada no diagrama de classes a 
seguir: 
 
No código a seguir, implementamos um memento para objetos da classe Pedido. A classe 
PedidoMemento representa o participante Memento do padrão, armazenando todas as informações 
necessárias para a restauração de um objeto Pedido. 
public class PedidoMemento { 
 private int numero; 
 private int numeroCorrente; 
 private List itens; 
 
 public PedidoMemento(int numeroPedido, int numeroCorrente, List 
itensOrigem) { 
 this.numero = numeroPedido; 
 this.numeroCorrente = numeroCorrente; 
 this.itens = new ArrayList(); 
 for (ItemPedido item : itensOrigem) { 
 this.itens.add( 
 new ItemPedidoMemento (item.getNumero(), item.getQuantidade(), 
 item.getCodigoProduto())); 
 } 
 } 
 public int getNumero() { 
 return numero; 
 } 
 public int getNumeroCorrente() { 
 return numeroCorrente; 
 } 
 public List getItens() { 
 return itens; 
 } 
 } 
A classe Pedido representa o participante Originator do padrão, pois é o seu estado que deve ser 
armazenado e restaurado por meio do uso de mementos. Veja o próximo código: 
public class Pedido { 
 private int numero; 
 private List itens; 
 private int numeroCorrente; 
 
 public Pedido(int numero) { 
 this.numero = numero; 
 itens = new ArrayList(); 
Padrões de Projetos de Software com Java 
Marcio Quirino - 78 
 
 numeroCorrente = 1; 
 } 
 
 public PedidoMemento criarMemento() { 
 return new PedidoMemento(numero, numeroCorrente, itens); 
 } 
 
 public void restaurarMemento(PedidoMemento memento) { 
 this.numero = memento.getNumero(); 
 this.numeroCorrente = memento.getNumeroCorrente(); 
 itens = new ArrayList(); 
 for (ItemPedidoMemento itemMemento : memento.getItens()) { 
 itens.add(new ItemPedido(itemMemento.getNumero(), itemMemento.getQuantidade(), 
 itemMemento.getCodigoProduto())); 
 } 
 } 
O código anterior ilustra duas operações adicionadas à classe Pedido original: 
A. Operação criarMemento 
✓ Salva o estado do pedido em uma instância de PedidoMemento. 
B. Operação restaurarMemento 
✓ Restaura o estado do pedido a partir do objeto PedidoMemento recebido como parâmetro. 
Veja no código a seguir um exemplo de implementação de uma operação undo utilizando um objeto 
da classe Pedido. Note que esse programa apenas guarda uma referência para o memento criado e 
retornado pelo objeto pedido, repassando-o quando for necessário restaurar o estado desse pedido. Em 
nenhum momento, o programa exemplo chama operações do objeto memento. 
public class Exemplo { 
 public static void main(String[] args) { 
 Pedido pedido = new Pedido(1); 
 pedido.adicionarItem(1, '1234'); 
 pedido.adicionarItem(2, '2345'); 
 // salva o estado atual do pedido, com dois itens 
 PedidoMemento memento = pedido.criarMemento(); 
 // adiciona o terceiro item ao pedido 
 pedido.adicionarItem(3, '3456'); 
 System.out.println(pedido.getItens().size()); // imprime o valor 3 
 // restaura a situação salva do pedido, com dois itens apenas 
 pedido.restaurarMemento(memento); 
 System.out.println(pedido.getItens().size()); // imprime o valor 2 
 } 
 } 
Consequências e padrões relacionados ao padrão Memento 
O padrão Memento facilita a implementação de problemas nos quais precisamos desfazer certas 
modificações de estado em objetos decorrentes da execução de operações ou implementar algum 
mecanismo de checkpoint/restart, em que interrompemos o processamento para retomá-lo posteriormente 
do ponto onde paramos. 
A implementação de um memento pode ser custosa em situações nas quais exista uma grande 
quantidade de informações para armazenar e posteriormente restaurar, especialmente quando envolver um 
objeto que tenha uma grande rede de objetos relacionados. Além disso, há peculiaridades na implementação 
do padrão Memento em algumas linguagens. A seguir, temos dois exemplos: 
A. Java 
✓ Dificulta a definição da interface do memento, de formaque somente o originador tenha 
acesso. 
B. C++ 
✓ Permite uma definição mais rigorosa por meio da utilização da palavra reservada friend. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 79 
 
Esse padrão é frequentemente utilizado com o padrão Command, quando este implementar um 
mecanismo para desfazer um comando (undo), pois, para isso, é necessário guardar o estado anterior à sua 
execução, o que pode ser feito com o uso do padrão Memento. 
Padrão Strategy 
O comportamento estratégico envolve uma tomada de decisão a partir da análise de condições 
ambientais que favoreçam o melhor conjunto de operações. Será por meio do padrão Strategy que iremos 
tornar nossos sistemas mais assertivos, agrupando um conjunto de operações em cada classe de estratégia, 
e invocando a classe que mais se adequa ao contexto de execução do aplicativo. Por exemplo, um sistema 
de detecção de vírus pode decidir entre manter, remover ou colocar um arquivo em quarentena, tendo como 
base a análise de código efetuada por um módulo de inteligência artificial. 
Intenção do padrão Strategy 
O padrão Strategy define uma família de algoritmos, encapsulando-os em objetos e permitindo que 
eles possam ser utilizados de forma intercambiável, ou seja, o algoritmo específico pode ser trocado sem 
que o módulo usuário desse algoritmo precise ser alterado. 
Problema resolvido pelo padrão Strategy 
O padrão Strategy é aplicável em situações nas quais existam diferentes algoritmos para gerar 
determinado resultado. Um exemplo desse tipo de situação é o cálculo dos juros de um título público. Você 
sabia que esse é um conhecimento fundamental para quem trabalha no mercado financeiro? 
Existem diferentes métodos para calcular os juros de um título para negociação em determinada 
data. Alguns exemplos de métodos são: 
• Regressão linear múltipla 
• Bootstrap 
• Interpolação polinominal splines cúbicos 
Imagine que você esteja desenvolvendo um módulo que calcula a taxa de juros de um título para 
negociação no dia seguinte. Uma solução frequentemente encontrada consiste em concentrar toda a lógica 
de cálculo em um único módulo, como ilustrado pela estrutura de código apresentada a seguir. É importante 
observar que esta é uma estrutura bastante simplificada da implementação real do problema, pois 
abstraímos a complexidade matemática envolvida. 
public class CurvaJuros { 
 private List pontos; // pontos que formam a curva de juros 
 
 // calcular a taxa de juros de um título para uma determinada data 
 public BigDecimal taxaJuros (Titulo titulo, Date data, TipoAlgoritmo tipo) { 
 switch (tipo) { 
 case TipoAlgoritmo.REGRESSAO_MULTIPLA: 
 return taxaPorRegressaoMultipla(titulo, data); 
 break; 
 case TipoAlgoritmo.BOOTSTRAP: 
 return taxaPorBootstrap(titulo, data); 
 break; 
 case TipoAlgoritmo.SPLINE: 
 return taxaPorSpline(titulo, data); 
 break; 
 } 
 } 
 private BigDecimal taxaPorRegressaoMultipla (Titulo titulo, Date data) { 
 // implementação do algoritmo por regressão múltipla 
 // acessa os dados da curva definidos no atributo pontos 
 } 
 private BigDecimal taxaPorBootstrap (Titulo titulo, Date data) { 
 // implementação do algoritmo pelo método bootstrap 
 // acessa os dados da curva definidos no atributo pontos 
Padrões de Projetos de Software com Java 
Marcio Quirino - 80 
 
 } 
 private BigDecimal taxaPorSpline (Titulo titulo, Date data) { 
 // implementação do algoritmo pelo método splines cúbicos 
 // acessa os dados da curva definidos no atributo pontos 
 } 
 } 
Esse tipo de solução possui dois problemas. Você já identificou quais são eles? Vejamos: 
A. Problema 1 
✓ Para adicionar novos algoritmos, temos que abrir o módulo TituloPublico para adicionar um 
novo método e um novo tipo ao switch/case do método taxaJuros, violando o princípio Open 
Closed, um dos princípios SOLID de projeto. 
B. Problema 2 
✓ Não é possível reutilizar o algoritmo para cálculo de outros valores que não sejam taxas de 
juros. Esses algoritmos são métodos matemáticos aplicáveis a outros problemas. Como 
podemos separar os algoritmos das classes de domínio em que eles são aplicados? 
Um problema similar ao problema 2 ocorre com os algoritmos de ordenação de dados, pois existem 
diferentes métodos de ordenação (Bubble Sort, Quick Sort, Merge Sort, Heap Sort, por exemplo) aplicáveis 
a diferentes tipos de dados (produtos, pessoas, números etc.). 
Solução do padrão Strategy 
A estrutura da solução proposta pelo padrão Strategy está representada no diagrama de classes a 
seguir. 
 
A ideia central consiste em separar cada algoritmo em uma classe, fazendo com que todas as classes 
específicas implementem uma interface comum, representada pelo participante Strategy. O participante 
Context define o contexto que proverá o algoritmo com os dados necessários para o processamento. Note 
que o contexto mantém uma referência para a interface genérica, não dependendo de qualquer 
implementação específica. Com isso, podemos adicionar novos algoritmos sem precisarmos modificar a 
classe que define o contexto para a sua aplicação. 
O código a seguir mostra como podemos implementar o problema do cálculo de juros de um título 
com a aplicação desse padrão. A interface CalculadoraJuros corresponde ao participante Strategy e define 
uma interface genérica para o cálculo da taxa a partir do título, da data para a qual a taxa será calculada e 
dos pontos da curva. Cada algoritmo é definido em uma classe que implementa essa interface, 
correspondendo ao participante ConcreteStrategy definido na estrutura do padrão. 
public interface CalculadoraJuros { 
 BigDecimal taxaJuros (Titulo titulo, Date data, List pontos); 
 } 
 
 public class CalculadoraJurosRegressaoMultipla implements CalculadoraJuros { 
Padrões de Projetos de Software com Java 
Marcio Quirino - 81 
 
 public BigDecimal taxaJuros (Titulo titulo, Date data, List pontos) { 
 // implementação do algoritmo por regressão múltipla 
 // acessa os dados da curva recebidos no parâmetro pontos 
 } 
 } 
 
 public class CalculadoraJurosBootstrap implements CalculadoraJuros { 
 public BigDecimal taxaJuros (Titulo titulo, Date data, List pontos) { 
 // implementação do algoritmo pelo método bootstrap 
 // acessa os dados da curva recebidos no parâmetro pontos 
 } 
 } 
 
 public class CalculadoraJurosSpline implements CalculadoraJuros { 
 public BigDecimal taxaJuros (Titulo titulo, Date data, List ponto) { 
 // implementação do algoritmo pelo método splines cúbicos 
 // acessa os dados da curva recebidos no parâmetro pontos 
 } 
 } 
Perceba que os algoritmos, antes implementados em operações de uma única classe, foram 
transformados em classes, todas implementando o mesmo conjunto de operações. A classe CurvaJuros 
passou a receber o algoritmo de cálculo como parâmetro, isto é, o algoritmo a ser utilizado passou a ser 
injetado dinamicamente no objeto, permitindo que ele trabalhe com qualquer algoritmo que implemente a 
interface genérica CalculadoraJuros. 
Portanto, a classe CurvaJuros corresponde ao participante Context do padrão, pois ela estabelece o 
contexto com os dados necessários para o processamento do algoritmo. É importante observar que essa 
injeção pode ser feita a cada chamada da operação taxaJuros, como mostrado no exemplo, ou apenas no 
construtor da classe CurvaJuros, o que faria com que cada instância de curva sempre trabalhasse com o 
algoritmo configurado na sua criação. 
publicclass CurvaJuros { 
 private List pontos; 
 
 // calcular a taxa de juros de um título para uma determinada data 
 public BigDecimal taxaJuros (Titulo titulo, Date data, CalculadoraJuros 
calculadoraJuros) { 
 return calculadoraJuros.taxaJuros (titulo, data, pontos); 
 } 
 } 
Veja no código a seguir como um módulo relacionado à classe CurvaJuros pode solicitar o cálculo 
da taxa, passando o algoritmo desejado para a sua execução. 
public class ClienteExemplo { 
 private CurvaJuros curva; 
 public ClienteExemplo() { 
 curva = new CurvaJuros(); 
 // processo de construção dos pontos da curva (omitido para simplificação) 
 } 
 public BigDecimal obterTaxa (Titulo titulo, Date data) { 
 // neste contexto, a taxa é calculada utilizando o método splines cúbicos 
 return curva.taxaJuros (titulo, data, new CalculadoraJurosSpline()); 
 } 
 } 
Consequências e padrões relacionados ao padrão Strategy 
O padrão Strategy oferece algumas vantagens: 
1. Definição de família de algoritmos 
✓ Permite a definição de uma família de algoritmos, que podem ser utilizados e configurados 
de forma flexível. 
2. Passos comuns na superclasse 
Padrões de Projetos de Software com Java 
Marcio Quirino - 82 
 
✓ Permite a implementação dos passos em comum dos diferentes algoritmos na superclasse, 
evitando a duplicação de código. 
3. Simplificação das estruturas 
✓ Evita a criação de estruturas condicionais complexas no contexto da aplicação dos algoritmos, 
substituindo comandos switch/case por chamadas polimórficas de operações. 
Por outro lado, o padrão Strategy expõe as diferentes opções de algoritmo para os clientes. Portanto, 
o uso desse padrão é mais indicado para as situações nas quais o cliente conheça e precise escolher o 
algoritmo mais apropriado. 
O algoritmo a ser utilizado pode ser parametrizado em uma configuração da aplicação, utilizando-se 
um padrão de criação (Factory Method ou Abstract Factory), ou ainda o recurso de injeção de dependências, 
para generalizar o processo de instanciação do algoritmo específico a ser utilizado. 
Atenção 
O padrão Strategy, por gerar objetos sem estado, pois os dados de que o algoritmo precisa estão definidos na 
classe contexto, pode ser implementado em combinação com o padrão Flyweight, de modo que uma única instância 
de cada algoritmo seja compartilhada pelos vários contextos. Em nosso exemplo, se fossem criadas 50 instâncias de 
CurvaJuros, bastaria instanciar um único objeto de cada algoritmo, caso adicionássemos o padrão Flyweight à solução. 
3. Padrões de projeto comportamentais Observer, Visitor e 
State 
Padrão Observer 
Atualmente o padrão Observer é utilizado em qualquer plataforma de desenvolvimento, como no 
relacionamento entre modelo e visualização do Android, ou nas chamadas HTTP assíncronas do Angular 
via RxJS. 
Segundo o padrão Observer, uma fonte de dados pode ser assinada por um conjunto de 
observadores, e qualquer modificação nos dados irá gerar uma notificação para os assinantes, permitindo a 
atualização de elementos associados em tempo real. Como exemplo simples, poderíamos construir uma 
planilha de gastos, em que um campo com o valor total e um gráfico agrupado por categoria seriam 
atualizados a cada novo lançamento na planilha. 
Intenção do padrão Observer 
O padrão Observer define uma relação de dependência entre objetos, de modo a garantir que, 
quando alguma modificação no estado de determinado objeto ocorrer, todos os objetos dependentes sejam 
notificados e atualizados automaticamente. 
Problema resolvido pelo padrão Observer 
Imagine que você esteja desenvolvendo um software para acompanhamento de vendas de uma 
empresa e que os dados de venda por produto e região sejam apresentados em três painéis 
simultaneamente. 
O primeiro painel é apresentado em tabela: 
Produto Região 1 Região 2 Região 3 Total 
A 40 30 100 170 
B 120 80 70 270 
C 80 50 90 220 
Total 240 160 260 660 
Tabela: Painel 1. Elaborada por: Alexandre Luis Correa. 
Os painéis 2 e 3 são apresentados em gráficos: 
Padrões de Projetos de Software com Java 
Marcio Quirino - 83 
 
 
Os três painéis apresentam dados oriundos de um sumário de vendas. Quando qualquer dado desse 
sumário mudar, os três painéis devem ser atualizados. 
O problema resolvido pelo padrão Observer consiste em manter a consistência entre objetos 
relacionados de modo que, se o estado de um objeto mudar, todos os objetos afetados por essa mudança 
sejam notificados e atualizados. 
Solução do padrão Observer 
O padrão Observer possui a estrutura definida no diagrama de classes a seguir: 
 
Vamos analisar cada participante do diagrama: 
A. Subject 
✓ O participante Subject define uma interface para registro (attach) e desligamento (detach) de 
observadores que devem ser notificados quando houver uma mudança no estado de um 
objeto concreto. Os observadores são armazenados em uma coleção mantida pelo Subject. 
B. Observer 
✓ O participante Observer define uma interface para o recebimento das notificações enviadas 
pelo Subject. 
C. ConcreteSubject 
✓ O participante ConcreteSubject corresponde a um elemento específico da aplicação que está 
sendo construída cujo estado, representado pelo atributo subjectState, é do interesse de um 
conjunto de observadores que serão notificados quando esse estado mudar. 
D. ConcreteObserver 
✓ O participante ConcreteObserver mantém uma referência para o objeto ConcreteSubject, 
armazenando ou apresentando dados, representados pelo atributo observerState, que devem 
se manter consistentes com o estado desse objeto. Ele implementa a interface de 
recebimento de notificação enviada pelo Subject (operação update), sendo responsável por 
Padrões de Projetos de Software com Java 
Marcio Quirino - 84 
 
obter o novo estado do ConcreteSubject, por meio das operações representadas pela 
operação GetState do participante Subject. 
O diagrama de sequência a seguir ilustra as interações entre os participantes da solução. 
Inicialmente, os objetos observadores devem se registrar no objeto Subject, por meio da operação attach. 
O estado de um objeto ConcreteSubject pode ser alterado, por meio das suas operações modificadoras, 
representadas genericamente pela operação setState. A implementação dessas operações modificadoras 
deve chamar a operação notify, definida para todo objeto do tipo Subject. A operação notify, por sua vez, 
deve invocar a operação update de todos os objetos observers registrados previamente. A implementação 
da operação update em cada ConcreteObserver deve obter o novo estado do objeto ConcreteSubject, 
invocando as operações de consulta representadas pela operação getState. 
 
Em Java, esse padrão pode ser implementado utilizando as classes disponíveis nas bibliotecas da 
linguagem. Com o Java 8, uma das implementações possíveis consiste em definir as classes notificadoras 
como subclasses de java.util.Observable, enquanto os observadores devem implementar a interface 
java.util.Observer. 
O código a seguir apresenta um exemplo de implementação desse padrão. A classe Ponto 
desempenha o papel de Subject. Nesse caso, Ponto é uma subclasse de Observable, herdando a 
implementação da gestão da lista de observadores e o método para notificação. Observe que todas as 
mudanças relevantes, presentes nos métodos setX e setY, chamam duas operações da superclasse 
Observable: setChanged e notifyObservers, que notificam todos os objetos observadores de que houve uma 
mudança no valor de um atributo. 
public class Ponto extends Observable { 
 private int x; 
 private int y; 
 
 public Ponto(int a, int b) { 
 this.x = a; 
 this.y = b; 
 } 
 public int getX() { 
Padrões de Projetos de Software com Java 
Marcio Quirino - 85 
 
 return x; 
 } 
 public void setX(int x) {this.x = x; 
 setChanged(); // chama operação definida na superclasse Observable 
 notifyObservers(); // chama operação definida na superclasse Observable 
 } 
 public int getY() { 
 return y; 
 } 
 public void setY(int y) { 
 this.y = y; 
 setChanged(); // chama operação definida na superclasse Observable 
 notifyObservers(); // chama operação definida na superclasse Observable 
 } 
 } 
Em seguida, definimos duas classes que desempenham o papel de observadores de modificações 
nas instâncias da classe Ponto. As duas classes implementam a operação update, definida na classe 
Observable, que é chamada pela operação notifyObservers. Assim, cada observador pode recuperar os 
dados atualizados do ponto e fazer qualquer tipo de atualização, como exibi-los na tela, por exemplo. 
public class Observador1 implements Observer { 
 public void update (Observable o, Object o1) { 
 if (o instanceof Ponto) { 
 Ponto exemplo = (Ponto) o; 
 System.out.println('Observador 1 => y = ' + exemplo.getY()); 
 } 
 } 
 } 
 
 public class Observador2 implements Observer { 
 public void update(Observable o, Object o1) { 
 if (o instanceof Ponto) { 
 Ponto exemplo = (Ponto) o; 
 System.out.println('Observador 2 => x = ' + exemplo.getX()); 
 System.out.println('Observador 2 => y = ' + exemplo.getY()); 
 } 
 } 
 } 
Vamos ver agora como o objeto subject e os observadores devem ser conectados para que o 
mecanismo funcione. O código a seguir apresenta um exemplo em que uma instância da classe Ponto é 
criada e dois observadores são adicionados a ela por meio da operação addObserver. A partir daí, toda vez 
que uma operação setX ou setY for executada, a operação update de cada observador será 
automaticamente chamada pelo mecanismo de notificação. 
public class ExemploObserver { 
 public static void main (String[] args) { 
 Ponto subject = new Ponto(2, 5); 
 Observador1 obs1 = new Observador1(); 
 Observador2 obs2 = new Observador2(); 
 
 subject.addObserver(obs1); // equivalente à operação attach definida no padrão 
 subject.addObserver(obs2); 
 
 subject.setX(10); // modifica o valor de x e notifica os observadores 
 subject.setY(10); // modifica o valor de y e notifica os observadores 
 subject.setX(20); // modifica o valor de x e notifica os observadores 
 } 
 } 
Consequências e padrões relacionados ao Observer 
O padrão Observer permite que os objetos detentores de informação relevante possam notificar que 
ocorreram modificações nessa informação aos possíveis interessados. Isso é feito por meio de uma interface 
genérica que facilita a adição de novos interessados. Esse padrão é especialmente utilizado na 
Padrões de Projetos de Software com Java 
Marcio Quirino - 86 
 
implementação de elementos de interface gráfica com o usuário que, além de gerarem eventos que precisam 
ser notificados para outros elementos, precisam ficar sincronizados com a sua fonte de dados. 
Uma consequência negativa desse padrão é que a solução proposta pode dar origem a muitas 
chamadas da operação update. Imagine uma atualização de diversas informações de um Subject com 
dezenas de observadores registrados. Nessa situação, seriam geradas centenas de chamadas da operação 
update. Portanto, certos problemas podem demandar a implementação de um protocolo específico de 
atualização, indicando quais modificações devem ser notificadas para cada observador e em que momento. 
Atenção 
O padrão Observer pode ser utilizado em conjunto com o padrão Mediator, especialmente quando certas 
modificações no Subject gerarem uma lógica complexa de atualizações em outros objetos. Em vez de estabelecermos 
diversas conexões diretas entre os observadores e os subjects, podemos inserir um mediador que centralize as 
notificações enviadas pelos subjects e coordenar a lógica de atualização dos diversos observadores. 
Padrão Visitor 
Segundo a definição do padrão Visitor, ele visa promover a separação entre uma família de objetos 
e os algoritmos que serão utilizados, permitindo que novas funcionalidades sejam definidas sem a 
necessidade de modificar os objetos. Por exemplo, você poderia definir uma interface Visitante que aceitasse 
planilhas, XML e JSON, implementando na classe GeradorHTML, que transformaria as informações em 
tabelas HTML, e na classe GeradorPDF, para gerar arquivos PDF a partir dos dados fornecidos, e, 
posteriormente, poderia adicionar a classe Base64Transform, que retornaria o conteúdo codificado em 
Base64, evoluindo o sistema sem afetar as funcionalidades definidas previamente. 
Intenção do padrão Visitor 
O padrão Visitor permite a definição de novas operações em uma hierarquia de objetos sem que haja 
a necessidade de modificar as classes dessa hierarquia. 
Problema resolvido pelo padrão Visitor 
Imagine que você esteja desenvolvendo um sistema para a área financeira que permite a 
parametrização de alguns cálculos por meio de expressões. 
Por exemplo, a expressão a + (10 * b) – (c / 5) pode ser representada pela seguinte estrutura de 
objetos: 
 
Essa estrutura de objetos pode ser percorrida para diferentes finalidades, tais como: verificar se a 
expressão é válida, calcular o valor da expressão, imprimir a expressão em formato texto, ou imprimir a 
expressão em notação polonesa. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 87 
 
Para cumprir todas essas finalidades, podemos acrescentar aos objetos dessa estrutura operações 
que possibilitem a validação de uma expressão, o cálculo de valor e assim por diante. Entretanto, sempre 
que quisermos acrescentar uma nova finalidade, teremos que modificar todas as classes dessa estrutura. 
O padrão Visitor nos permite acrescentar novas funcionalidades sem precisar modificar as classes 
que representam a estrutura fundamental dos objetos que compõem a expressão. 
Solução do padrão Visitor 
A estrutura da solução proposta pelo padrão Visitor é apresentada no diagrama UML a seguir: 
 
A solução consiste em separar os elementos que formam uma estrutura hierárquica e os algoritmos 
que realizam operações sobre essa estrutura em duas famílias de classes. Vamos analisar dois participantes 
da solução apresentada: 
A. Visitor 
✓ O participante Visitor define uma família de operações, que podem aplicadas a cada um dos 
elementos concretos da estrutura. Para cada classe concreta da estrutura, correspondente 
ao participante ConcreteElement, deve ser definida uma operação visit, 
que recebe como parâmetro um objeto da respectiva classe. Cada família de operações é 
definida em um ConcreteVisitor específico. 
B. Element 
✓ O participante Element corresponde à classe mais genérica da estrutura hierárquica dos 
elementos, na qual é definida a operação accept, que recebe um visitor como parâmetro e 
chama a operação visitConcreteElement correspondente à sua classe, passando o próprio 
objeto que está sendo visitado como argumento. 
O código a seguir, ilustra a implementação do problema das expressões aritméticas, utilizando o 
padrão Visitor. Para simplificar, vamos mostrar apenas somas de números inteiros. Cada número é uma 
instância da classe NumeroInteiro, enquanto cada operador de soma é uma instância da classe OpSoma. 
Todo operador aritmético herda da classe OperadorAritmetico que define dois operandos, um à esquerda e 
Padrões de Projetos de Software com Java 
Marcio Quirino - 88 
 
o outro à direita do operador. Todo elemento de uma expressão, seja um número, seja um operador, 
implementa a operação accept definida na superclasse ElementoExpressao. 
public abstract class ElementoExpressao { 
 public abstract void accept(VisitorExpressaoAritmetica visitor); 
 } 
 
 public82 
Padrão Observer ...................................................................................................................................... 82 
Intenção do padrão Observer ................................................................................................................... 82 
Problema resolvido pelo padrão Observer ........................................................................................... 82 
Solução do padrão Observer ................................................................................................................ 83 
Consequências e padrões relacionados ao Observer .......................................................................... 85 
Padrão Visitor ........................................................................................................................................... 86 
Intenção do padrão Visitor ....................................................................................................................... 86 
Padrões de Projetos de Software com Java 
Marcio Quirino - 4 
 
Problema resolvido pelo padrão Visitor ................................................................................................ 86 
Solução do padrão Visitor .................................................................................................................... 87 
Consequências e padrões relacionados ao Visitor ............................................................................... 89 
Padrão State ............................................................................................................................................ 90 
Intenção do padrão State ..................................................................................................................... 90 
Problema resolvido pelo padrão State ................................................................................................. 90 
Solução do padrão State ...................................................................................................................... 91 
Consequências e padrões relacionados ao State ................................................................................ 92 
4. Padrões de projeto comportamentais Interpreter e Template Method ................................. 92 
Padrão Interpreter .................................................................................................................................... 92 
Intenção do padrão Interpreter ............................................................................................................. 92 
Problema do padrão Interpreter ........................................................................................................... 92 
Solução do padrão Interpreter .............................................................................................................. 92 
Consequências e padrões relacionados ao Interpreter ........................................................................ 93 
Padrão Template Method ......................................................................................................................... 94 
Intenção do padrão Template Method .................................................................................................. 94 
Problema do padrão Template Method ................................................................................................ 94 
Solução do padrão Template Method .................................................................................................. 94 
Consequências e padrões relacionados ao Template Method ............................................................. 95 
Explore + .................................................................................................................................................. 96 
Referências .............................................................................................................................................. 96 
Padrões GRASP .................................................................................................................................. 97 
Introdução ................................................................................................................................................ 97 
1. Padrões Especialista na Informação e Criador ..................................................................... 97 
O padrão Especialista na Informação ...................................................................................................... 97 
Solução do Especialista na Informação................................................................................................ 98 
Consequências do Especialista na Informação .................................................................................. 101 
O padrão Criador .................................................................................................................................... 101 
Solução do Criador ............................................................................................................................. 101 
Consequências do Criador ................................................................................................................. 103 
2. Padrões Coesão Alta e Controlador .................................................................................... 103 
O padrão Coesão Alta ............................................................................................................................ 103 
Solução da Coesão Alta ..................................................................................................................... 104 
Coesão coincidente ............................................................................................................. 104 
Coesão lógica ...................................................................................................................... 104 
Coesão temporal ................................................................................................................. 105 
Coesão procedural .............................................................................................................. 106 
Coesão de comunicação ..................................................................................................... 106 
Coesão sequencial .............................................................................................................. 107 
Coesão funcional ................................................................................................................. 107 
Padrões de Projetos de Software com Java 
Marcio Quirino - 5 
 
Consequências da Coesão Alta ......................................................................................................... 108 
O padrão Controlador ............................................................................................................................. 108 
Solução do Controlador ...................................................................................................................... 108 
Consequências do Controlador .......................................................................................................... 109 
3. Padrões Acoplamento Baixo e Polimorfismo ...................................................................... 110 
O padrão Acoplamento Baixo................................................................................................................. 110 
Solução do Acoplamento Baixo .......................................................................................................... 111 
Acoplamento de conteúdo .................................................................................................. 111 
Acoplamento global .............................................................................................................class NumeroInteiro extends ElementoExpressao { 
 private int valor; 
 public NumeroInteiro(int valor) { 
 this.valor = valor; 
 } 
 public void accept(VisitorExpressaoAritmetica visitor) { 
 visitor.visitNumeroInteiro(this); 
 } 
 public int getValor() { 
 return valor; 
 } 
 } 
 
 public abstract class OperadorAritmetico extends ElementoExpressao { 
 private ElementoExpressao operandoEsquerdo; 
 private ElementoExpressao operandoDireito; 
 
 public OperadorAritmetico(ElementoExpressao operandoEsquerdo, ElementoExpressao 
operandoDireito) { 
 this.operandoEsquerdo = operandoEsquerdo; 
 this.operandoDireito = operandoDireito; 
 } 
 public abstract String getOperador(); 
 public ElementoExpressao getOperandoEsquerdo() { 
 return operandoEsquerdo; 
 } 
 public void setOperandoEsquerdo(ElementoExpressao operandoEsquerdo) { 
 this.operandoEsquerdo = operandoEsquerdo; 
 } 
 public ElementoExpressao getOperandoDireito() { 
 return operandoDireito; 
 } 
 public void setOperandoDireito(ElementoExpressao operandoDireito) { 
 this.operandoDireito = operandoDireito; 
 } 
 } 
 
 public class OpSoma extends OperadorAritmetico { 
 public OpSoma(ElementoExpressao operandoEsquerdo, ElementoExpressao operandoDireito) { 
 super(operandoEsquerdo, operandoDireito); 
 } 
 public String getOperador() { 
 return '+'; 
 } 
 public void accept(VisitorExpressaoAritmetica visitor) { 
 visitor.visitOpSoma(this); 
 } 
 } 
A avaliação do resultado de uma expressão é definida em um Visitor separadamente da estrutura da 
expressão. A interface VisitorExpressaoAritmetica define uma operação visitor para cada elemento da 
estrutura, enquanto a classe VisitorCalculadora implementa as operações necessárias para calcular o valor 
de uma expressão. 
Note que a navegação pelos elementos da estrutura é definida no visitor. Por exemplo, a operação 
soma precisa, em primeiro lugar, avaliar a expressão do operando à esquerda do operador, para depois 
avaliar a expressão à direita, e finalmente gerar o valor da expressão, somando o resultado das duas 
expressões. 
public interface VisitorExpressaoAritmetica { 
Padrões de Projetos de Software com Java 
Marcio Quirino - 89 
 
 void visitNumeroInteiro(NumeroInteiro numero); 
 void visitOpSoma(OpSoma opSoma); 
 } 
 
 public class VisitorCalculadora implements VisitorExpressaoAritmetica { 
 private int valor; 
 public int getValor() { 
 return valor; 
 } 
 public void visitNumeroInteiro(NumeroInteiro numero) { 
 valor = numero.getValor(); 
 } 
 public void visitOpSoma(OpSoma opSoma) { 
 valor = obterValorExpressao(opSoma.getOperandoEsquerdo()) + 
 obterValorExpressao(opSoma.getOperandoDireito()); 
 } 
 private int obterValorExpressao(ElementoExpressao expressao) { 
 VisitorCalculadora visitor = new VisitorCalculadora(); 
 expressao.accept(visitor); 
 return visitor.getValor(); 
 } 
 } 
 
 public class TestCalculadora { 
 public static void main(String[] args) { 
 // definição da expressão 10 + 20 + 30 
 ElementoExpressao expressao = 
 new OpSoma( 
 new OpSoma(new NumeroInteiro(10), new NumeroInteiro(20)), 
 new NumeroInteiro(30)); 
 
 VisitorCalculadora visitorCalc = new VisitorCalculadora(); 
 expressao.accept(visitorCalc); 
 System.out.println('resultado = ' + visitorCalc.getValor()); 
 } 
 } 
Praticar é a melhor forma de fixar o conteúdo. Nesse sentido, recomendamos as seguintes 
atividades: 
• Implemente as operações de multiplicação, divisão e subtração, para o exemplo. 
• Implemente outro visitor capaz de imprimir a expressão. Ex: 10 + 20 
Consequências e padrões relacionados ao Visitor 
O padrão Visitor permite a adição de novas funcionalidades de forma ortogonal a uma estrutura de 
objetos. Como vimos no exemplo das expressões, diversas funcionalidades podem ser implementadas como 
um visitor. Veja alguns exemplos: 
• Cálculo 
• Formatação 
• Verificação sintática 
• Verificação semântica da expressão 
Desse modo, a estrutura original de objetos não fica poluída com operações não relacionadas entre 
si. Entretanto, a adição de um novo elemento à estrutura de objetos afeta todos os visitors implementados, 
pois será necessário adicionar uma operação de visita em cada uma dessas classes. Portanto, o padrão 
Visitor não é adequado para estruturas que mudem com frequência. 
Atenção 
O padrão Visitor pode ser utilizado em conjunto com os padrões Composite e Interprete. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 90 
 
Padrão State 
Diversos elementos assumem comportamentos diferentes de acordo com o estado corrente, como 
no caso de uma porta automática, na qual o sensor indicando a presença de uma pessoa causaria a 
abertura, e a ausência o fechamento da porta. Um exemplo na área de sistemas seria o download de 
arquivos, no qual teríamos os estados requerido, iniciado, executando e concluído, com diferentes 
informações sendo exibidas a cada instante, como o percentual baixado durante a execução. Por meio da 
utilização do padrão State, seremos capazes de encapsular o tratamento dessas informações em classes 
separadas para cada estado da operação, deixando nosso código mais limpo e organizado. 
Intenção do padrão State 
O padrão State permite que um objeto modifique o seu comportamento quando o seu estado mudar, 
como se o objeto tivesse mudado de classe. Em vez de uma única classe tratar os estados dos seus objetos 
em operações com diversas expressões condicionais, cada estado é representado em uma classe separada. 
Problema resolvido pelo padrão State 
O padrão State é muito útil para problemas envolvendo a implementação de entidades com uma 
dinâmica de estados relevante. Por exemplo, suponha um sistema de ponto de venda de uma loja. Um ponto 
de venda pode estar fechado, disponível para vendas ou com uma venda em curso. Eventos provocam a 
transição entre esses estados. Por exemplo, no início do dia, o caixa está fechado e ao receber o evento 
iniciar dia, ele passa para o estado disponível. Um mesmo evento pode levar para diferentes estados, como 
é o caso do evento iniciar sangria, que pode ocorrer quando o PDV está no estado Disponível ou Vendendo. 
 
O código a seguir ilustra uma implementação convencional sem a utilização do padrão. Note como 
essa implementação é baseada em código condicional embasado no estado do PDV. Para uma máquina de 
estados mais complexa, com mais estados e eventos, esse tipo de solução torna o código muito complexo, 
difícil de testar e modificar. 
public class PDV { 
 private int estado; 
 
 public iniciarSangria() { 
 if (estado == ESTADO_PDV.Disponivel) { 
 // tratamento da sangria quando o PDV está disponível 
 // estado = SangriaNormal; 
 } else if (estado == ESTADO_PDV.Vendendo) { 
 // tratamento da sangria quando o PDV está vendendo 
 // estado = SangriaExpressa; 
 } else { 
 // evento não deve ser aceito 
 } 
 } 
Padrões de Projetos de Software com Java 
Marcio Quirino - 91 
 
Solução do padrão State 
A estrutura da solução proposta pelo padrão State é apresentada no diagrama UML a seguir. O 
participante Context corresponde a uma classe que possui uma dinâmica dependente de estados. Cada 
classe concreta ConcreteState implementa o comportamento da classe Context associado a um estado 
específico. A classe Context possui um atributo do tipo State, que correspondeao estado corrente do objeto. 
Quando um objeto Context recebe uma requisição, a execução é passada para o estado corrente por meio 
da operação handle definida na interface State. 
 
O exemplo a seguir mostra como o PDV descrito no problema pode ser implementado com o padrão. 
Essa é uma implementação parcial apenas para você entender a estrutura da solução. A classe PDV 
corresponde ao participante Context. Ele possui um atributo do tipo PDV_Estado, que corresponde ao 
participante State. PDV_Estado é uma interface que define todos os eventos que o contexto deve tratar. 
Cada evento é definido como uma operação distinta. 
As classes PDV_Estado_Disponivel e PDV_EstadoVendendo são dois exemplos de classes 
concretas que correspondem a implementações distintas da interface genérica de eventos. 
public class PDV { 
 private PDV_Estado estadoCorrente; 
 
 public void mudarEstado(PDV_Estado estado) { 
 this.estadoCorrente = estado; 
 } 
 public iniciarSangria() { 
 // contexto é passado como argumento para a operação do estadoCorrente 
 estadoCorrente.iniciarSangria(this); 
 } 
 } 
 public abstract class PDV_Estado { 
 public abstract void iniciarSangria(PDV pdv); 
 } 
 public class PDV_Estado_Disponivel() extends PDV_Estado { 
 public void iniciarSangria(PDV pdv) { 
 // inicia o processo de sangria normal 
 contexto.mudarEstado (new PDV_Estado_SangriaNormal()); 
 } 
 } 
 public class PDV_Estado_Vendendo() extends PDV_Estado { 
 public void iniciarSangria (PDV pdv) { 
 // inicia o processo de sangria expressa 
 contexto.mudarEstado (new PDV_Estado_SangriaExpressa()); 
 } 
 } 
Padrões de Projetos de Software com Java 
Marcio Quirino - 92 
 
Consequências e padrões relacionados ao State 
O padrão State separa os comportamentos aplicáveis a cada estado de um objeto em classes 
distintas. Por outro lado, essa solução gera um número bem maior de classes, o que pode ser bom, 
especialmente quando existirem muitos estados e muitas operações que dependam desses estados. 
Esse padrão também melhora a compreensão do código, pois além de eliminar código condicional 
extenso baseado no estado corrente, ele explicita as transições de estado. 
Atenção 
O padrão State pode ser combinado com o padrão Flyweight, permitindo o compartilhamento de objetos 
estados por muitos objetos contextuais, evitando a criação de um objeto estado para cada objeto contexto. 
4. Padrões de projeto comportamentais Interpreter e 
Template Method 
Padrão Interpreter 
O padrão Interpreter, como seu nome sugere, define um interpretador de comandos, normalmente 
baseado em um conjunto de regras sintáticas. 
Um dos melhores exemplos para adoção do padrão Interpreter é na compilação de expressões 
regulares, comuns na filtragem e identificação de conteúdo em sequências de texto. Outro exemplo seria os 
dados fornecidos por satélites meteorológicos, que são codificados na forma de texto, segundo regras bem 
definidas. O padrão Interpreter pode ser utilizado para controlar as regras de leitura desses dados e 
consequente extração das informações, permitindo alimentar outros sistemas, como na visualização de 
mapas meteorológicos. 
Intenção do padrão Interpreter 
O propósito do padrão Interpreter é definir uma representação para a gramática de uma linguagem 
e um módulo capaz de interpretar sentenças nessa linguagem. 
Problema do padrão Interpreter 
Em sistemas que trabalham com cálculos customizáveis, uma solução comum consiste em definir 
esses cálculos utilizando expressões matemáticas. Uma expressão matemática deve seguir uma gramática 
que estabelece as regras de formação das expressões. 
O problema resolvido pelo padrão Interpreter consiste em definir uma forma de representar e 
interpretar uma linguagem definida por uma gramática. 
Solução do padrão Interpreter 
A estrutura da solução proposta pelo padrão Interpreter está representada no diagrama de classes a 
seguir. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 93 
 
 
Os elementos da gramática da linguagem são definidos por objetos que formam uma árvore sintática 
abstrata. Os símbolos terminais correspondem ao participante TerminalExpression, enquanto os elementos 
compostos por outros correspondem ao participante NonTerminalExpression. 
Veja, na imagem a seguir, como a expressão a + (10 * b) – (c / 5) pode ser representada por uma 
estrutura hierárquica de objetos. 
 
Atenção 
Nessa gramática, constantes e variáveis são elementos terminais, enquanto os operadores de soma, 
subtração, multiplicação e divisão são elementos não terminais, estando ligados a um elemento à esquerda e outro à 
direita. 
Consequências e padrões relacionados ao Interpreter 
Esse padrão facilita a modificação e a ampliação de uma gramática. A ideia é implementar a estrutura 
com uma classe simples para cada elemento da gramática, em vez de tentar centralizar a solução em uma 
única classe. O padrão é: 
A. Indicado 
✓ Para linguagens com uma gramática simples, como é o caso, por exemplo, das expressões 
aritméticas. 
B. Não indicado 
✓ Para linguagens mais complexas, recomenda-se utilizar outras soluções, como ferramentas 
que gerem automaticamente interpretadores. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 94 
 
A operação interpret, definida nos participantes do padrão, pode ser vista como um processamento 
específico que se deseja realizar com esses elementos. Por exemplo, no caso das expressões aritméticas, 
o processamento poderia ser calcular o resultado. Entretanto, podem existir outros processamentos, como: 
• Verificação sintática 
• Verificação semântica 
• Obtenção do texto da expressão 
Nesses casos, recomenda-se aplicar o padrão Visitor em conjunto com o Interpreter, em que o 
Interpreter define a estrutura dos elementos da linguagem, enquanto cada processamento é implementado 
em uma classe Visitor específica. 
Padrão Template Method 
Hoje em dia é comum a utilização de frameworks na área de desenvolvimento, sendo definido um 
modelo funcional padrão, que é personalizado pelo desenvolvedor para as regras de negócio do aplicativo. 
Por meio do padrão Template Method, podemos definir macroprocessos com lacunas especificadas em 
métodos abstratos, permitindo ao programador completar a funcionalidade exigida por meio de polimorfismo, 
como ocorre nos frameworks. Um exemplo prático seria a implementação do algoritmo HMAC, que exige 
uma cifra assimétrica e um algoritmo de hash, utilizados em uma sequência de chamadas bem definida, 
permitindo que um desenvolvedor trabalhe com RSA e SHA-3, enquanto outro utiliza ECDSA e SHA-2. 
Intenção do padrão Template Method 
O propósito do padrão Template Method é definir o esqueleto de um algoritmo em uma superclasse, 
em que os passos comuns podem ser implementados na própria superclasse e os passos específicos são 
implementados nas subclasses. 
Problema do padrão Template Method 
Suponha que você esteja implementando um sistema que deve gerar alguns relatórios textuais. A 
preparação dos dados para qualquer relatório segue três passos fundamentais: gerar o cabeçalho, gerar o 
corpo e gerar o sumário. 
Imagine, por exemplo, que você tenha que implementar um relatório de vendas e um relatório de 
devoluções em um período. O código a seguir ilustra uma solução inicial para esses relatórios. 
public class ServicoRelatorio { 
 public void gerarRelatorioVendas (Date dataInicial, Date dataFinal) { 
 // código para gerar cabeçalho do relatório de vendas 
 // código para gerar corpo do relatório de vendas 
 // código para gerar sumário do relatório de vendas 
 } 
 public void gerarRelatorioDevolucoes (Date dataInicial, Date dataFinal) { 
 // código para gerar cabeçalho do relatóriode devoluções 
 // código para gerar corpo do relatório de devoluções 
 // código para gerar sumário do relatório de devoluções 
 } 
 } 
Essa solução não é adequada, primeiro por ser baseada em repetição de código com estrutura muito 
similar, apesar de diferirem nos detalhes de geração de cada relatório. Segundo, para cada novo relatório, 
é necessário abrir a classe ServicoRelatorio e acrescentar o código específico desse relatório, 
provavelmente copiando, colando e adaptando o código de um dos relatórios já implementados. 
Solução do padrão Template Method 
A estrutura da solução proposta pelo padrão TemplateMethod está representada no diagrama de 
classes a seguir: 
Padrões de Projetos de Software com Java 
Marcio Quirino - 95 
 
 
O padrão sugere definir um método em uma classe abstrata que implemente a estrutura do algoritmo 
comum a todas as suas implementações específicas. A estrutura desse algoritmo é composta por um 
conjunto de passos que podem ser redefinidos pelas subclasses. Cada ponto de variação no algoritmo 
corresponde à definição de uma operação na superclasse que pode ser especializada nos seus 
descendentes. Esses pontos de variação podem ser: 
1. Operações abstratas 
✓ Tais operações obrigarão as subclasses a implementá-las. 
2. Métodos hook (gancho) 
✓ Operações realizadas na superclasse e que podem ser substituídas por implementações 
específicas em uma ou mais subclasses. 
O código a seguir ilustra a estrutura de implementação para os relatórios com a aplicação desse 
padrão. A classe abstrata Relatorio define três operações abstratas que serão implementadas por todas as 
subclasses. A operação gerar é o método padrão (template method) que define a estrutura comum do 
algoritmo, seguida por todos os relatórios. Desse modo, cada relatório define as particularidades da geração 
de cabeçalho, corpo e sumário na implementação dessas operações genéricas. 
public abstract class Relatorio { 
 public abstract void gerarCabecalho(); 
 public abstract void gerarCorpo(); 
 public abstract void gerarSumario(); 
 public void gerar() { // template method 
 gerarCabecalho(); 
 gerarCorpo(); 
 gerarSumario(); 
 } 
 } 
 public class RelatorioVendas extends Relatorio { 
 public void gerarCabecalho() { 
 // código para gerar o cabeçalho do relatório de vendas 
 } 
 public void gerarCorpo() { 
 // código para gerar o corpo do relatório de vendas 
 } 
 public void gerarSumario() { 
 // código para gerar o sumário do relatório de vendas 
 } 
 } 
Outro benefício dessa implementação é que o código em comum entre os diferentes relatórios pode 
ser efetuado uma única vez na superclasse Relatório, reduzindo a quantidade de código repetido. 
Consequências e padrões relacionados ao Template Method 
O padrão Template Method é muito utilizado na implementação de bibliotecas genéricas de classes 
e de frameworks. O resultado obtido pela sua aplicação é conhecido como o princípio de Hollywood, isto é, 
Padrões de Projetos de Software com Java 
Marcio Quirino - 96 
 
“Não nos chame, nós o chamaremos”, uma referência a como a superclasse chama as operações 
específicas das subclasses e não o contrário. 
O padrão Factory Method costuma ser utilizado no contexto de um Template Method, nos casos em 
que um dos passos do algoritmo seja instanciar algum objeto específico. Os padrões Template Method e 
Strategy são formas de representação de algoritmos: 
1. Template Method 
✓ Permite a variação de partes de um algoritmo, por meio de uma estrutura de herança. 
2. Strategy 
✓ É aplicado quando desejamos criar algoritmos diferentes, mas que não possuam uma 
estrutura comum. 
Explore + 
Para saber mais sobre a programação orientada a objetos, acesse o site da DevMedia e leia o artigo 
Utilização dos princípios SOLID na aplicação de padrões de projeto. 
O site Padrões de Projeto / Design patterns – Refactoring.Guru apresenta um conteúdo interativo e 
bastante completo de todos os padrões GoF, com exemplos de código em diversas linguagens de 
programação. 
Além dos padrões GoF tradicionais, outros padrões voltados para o desenvolvimento de aplicações 
corporativas em Java EE podem ser encontrados no livro Java EE 8 Design Patterns and Best Practices, de 
Rhuan Rocha e João Purificação, de 2018. A obra aborda padrões para interface com o usuário, lógica do 
negócio, integração de sistemas, orientação a aspectos, programação reativa e microsserviços. 
Referências 
GAMMA, E.; HELM, R.; JOHNSON, R.; VLISSIDES, J. Design Patterns: Elements of Reusable 
Object-Oriented Software. 1. ed. Boston: Addison-Wesley, 1994. 
METSKER, S. J.; WAKE, W. C. Design Patterns in Java. 1. ed. Boston: Addison-Wesley, 2006. 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 97 
 
Padrões GRASP 
Introdução 
GRASP é o acrônimo para o termo em inglês general responsibility assignment software patterns 
proposto por Craig Larman no livro Applying UML and patterns, que define as diretrizes para a atribuição de 
responsabilidades em software. Os padrões GRASP podem ser vistos como os princípios gerais de um 
projeto de software orientado a objetos que são aplicáveis na solução de diversos problemas específicos. 
A distribuição de responsabilidades pelos módulos do sistema é uma das tarefas mais importantes 
no desenvolvimento orientado a objetos. Diagramas UML são veículos que nos permitem expressar e discutir 
decisões sobre as responsabilidades de cada módulo. 
No entanto, as decisões tomadas é que realmente são importantes. Com isso, seguir padrões e 
princípios bem estabelecidos aumenta nossas chances de tomar decisões que resultem em um software 
mais fácil de ser evoluído ao longo da sua existência. Neste conteúdo, você vai conhecer os padrões GRASP 
e entender os problemas de projeto de software que eles resolvem. 
1. Padrões Especialista na Informação e Criador 
O padrão Especialista na Informação 
No desenvolvimento de um sistema orientado a objetos, é comum elaborar um modelo UML 
representando os principais conceitos e dados no escopo do sistema por meio de classes, atributos e 
associações. Entretanto, quando se elabora a solução técnica de um projeto, as principais questões a serem 
respondidas passam a ser: 
Quais responsabilidades cada classe deve possuir? 
Quais serão as interações necessárias entre os objetos dessas classes para o sistema realizar as 
funcionalidades esperadas? 
Suponha que você esteja desenvolvendo um sistema de venda de produtos pela internet. A figura 1 
apresenta um modelo simplificado de classes desse domínio. 
 
Digamos que o sistema deva listar os itens e o valor total do pedido do cliente, sendo que esse valor 
é uma informação calculada a partir dos produtos e das suas respectivas quantidades compradas. Como 
você organizaria as responsabilidades entre as classes para fornecer essa informação? 
Padrões de Projetos de Software com Java 
Marcio Quirino - 98 
 
Solução do Especialista na Informação 
Este padrão recomenda como princípio geral a atribuição correta de responsabilidade. Você pode 
estar se perguntando: mas o que isso significa? 
Atribua a responsabilidade ao especialista, isto é, ao módulo que possua o conhecimento 
necessário para realizá-la. 
Atribuir a responsabilidade ao especialista é uma heurística intuitiva que utilizamos no nosso 
cotidiano. Suponha que você precise trocar o encanamento da sua casa. A quem você atribuiria essa 
responsabilidade? Provavelmente você recorrerá a um especialista para realizar tal atividade. 
Atenção 
A transposição dessa heurística do nosso cotidiano para o mundo de objetos em software é conhecida por 
heurística antropomórfica, ou seja, imaginamos os objetos como pessoas que possuem determinado conhecimento e 
que podem utilizá-lo para realizar algumas tarefas. Comoprojetista de software, você assume o papel de diretor desse 
universo de objetos, podendo definir quantas classes quiser – cada qual com as responsabilidades que você achar 
melhor. 
Voltemos para o nosso problema do sistema de venda de produtos: o valor total do pedido pode ser 
definido como a soma do valor de cada um de seus itens. Segundo o padrão Especialista na Informação, a 
responsabilidade tem de ficar com o detentor da informação. 
Nesse caso, quem conhece todos os itens que compõem um pedido? O próprio pedido, não é 
mesmo? Então que tal definir uma operação obterValorTotal na classe Pedido? Mas onde ficaria o cálculo 
do preço de cada item do pedido? Na própria classe Pedido? Pensemos um pouco mais nessa questão: 
1. Quais informações são necessárias para esse cálculo? 
✓ A quantidade e o preço do produto, já que o valor de um item é a multiplicação desses dois 
valores. 
2. Quem conhece essas informações? 
✓ A classe ItemPedido conhece a quantidade e o produto associado. 
Vamos definir, portanto, uma operação obterValor na classe ItemPedido. E o preço do produto? Basta 
o objeto item do pedido solicitar essa informação ao produto (acessível pelo relacionamento entre eles) por 
meio da operação de acesso getPrecoUnitario. 
O código a seguir apresenta a implementação das classes Pedido, ItemPedido e Produto, 
considerando essa distribuição de responsabilidades: 
public class Pedido { 
 private int numero; 
 private Date data; 
 private List itens; 
 
 public BigDecimal obterValorTotal() { 
 BigDecimal resultado = new BigDecimal(0); 
 itens.forEach(item -> { 
 resultado.add(item.obterValor()); 
 }); 
 return resultado; 
 } 
 public int getNumero() { 
 return numero; 
 } 
 public void setNumero(int numero) { 
 this.numero = numero; 
 } 
 public Date getData() { 
 return data; 
 } 
 public void setData(Date data) { 
Padrões de Projetos de Software com Java 
Marcio Quirino - 99 
 
 this.data = data; 
 } 
 public Iterator getItens() { 
 return itens.iterator(); 
 } 
 public void adicionarItem(int quantidade, Produto produto) { 
 itens.add(new ItemPedido(itens.size() + 1, quantidade, produto)); 
 } 
 } 
 
 public class ItemPedido { 
 private int numero; 
 private int quantidade; 
 private Produto; 
 
 public ItemPedido(int numero, int quantidade, Produto) { 
 this.numero = numero; 
 this.quantidade = quantidade; 
 this.produto = produto; 
 } 
 public BigDecimal obterValor() { 
 return produto.getPrecoUnitario().multiply(new BigDecimal(quantidade)); 
 } 
 public int getNumero() { 
 return numero; 
 } 
 public void setNumero(int numero) { 
 this.numero = numero; 
 } 
 public int getQuantidade() { 
 return quantidade; 
 } 
 public void setQuantidade(int quantidade) { 
 this.quantidade = quantidade; 
 } 
 public Produto getProduto() { 
 return produto; 
 } 
 public void setProduto(Produto produto) { 
 this.produto = produto; 
 } 
 } 
 
 public class Produto { 
 private String codigo; 
 private String descricao; 
 private BigDecimal precoUnitario; 
 private File imagem; 
 
 public String getCodigo() { 
 return codigo; 
 } 
 public void setCodigo(String codigo) { 
 this.codigo = codigo; 
 } 
 public String getDescricao() { 
 return descricao; 
 } 
 public void setDescricao(String descricao) { 
 this.descricao = descricao; 
 } 
 public BigDecimal getPrecoUnitario() { 
 return precoUnitario; 
 } 
 public void setPrecoUnitario(BigDecimal precoUnitario) { 
 this.precoUnitario = precoUnitario; 
 } 
 public File getImagem() { 
 return imagem; 
Padrões de Projetos de Software com Java 
Marcio Quirino - 100 
 
 } 
 public void setImagem(File imagem) { 
 this.imagem = imagem; 
 } 
 } 
Classes Pedido, ItemPedido e Produto. 
O diagrama de sequência modela a colaboração entre os objetos que implementam o cálculo do 
valor total de um pedido, como exemplificada na figura 2. 
 
Uma alternativa para a alocação da responsabilidade pelo cálculo do valor de um item do pedido 
seria considerar a classe Produto como a especialista em preço. No entanto, ela precisa de uma informação 
adicional (a quantidade) para cumprir essa responsabilidade. 
Desse modo, podemos definir a operação obterValorParaQuantidade na classe Produto, fazendo 
com que a operação obterValor (definida em ItemPedido) passe a quantidade como argumento. Nesse caso, 
o cálculo do valor para determinada quantidade fica na classe Produto, que pode eventualmente aplicar 
políticas de desconto específicas conforme a quantidade e o tipo do produto, por exemplo. O código a seguir 
apresenta a implementação dessa alternativa de alocação de responsabilidades: 
public class ItemPedido { 
 public ItemPedido(int numero, int quantidade, Produto produto) { 
 this.numero = numero; 
 this.quantidade = quantidade; 
 this.produto = produto; 
 } 
 public BigDecimal obterValor() { 
 return produto.obterPrecoParaQuantidade(quantidade); 
 } 
 … // restante da classe ItemPedido 
 } 
 
 public class Produto { 
 private String codigo; 
 private String descricao; 
 private BigDecimal precoUnitario; 
 private File imagem; 
 
 public BigDecimal obterPrecoParaQuantidade(int quantidade) { 
 return precoUnitario.multiply(quantidade); 
 } 
 // restante da classe Produto 
 } 
Padrões de Projetos de Software com Java 
Marcio Quirino - 101 
 
Consequências do Especialista na Informação 
Normalmente, a realização de uma funcionalidade do sistema envolve a presença de diversos 
especialistas, pois cada classe possui uma parte das informações necessárias para resolver o problema. 
Dessa forma, será necessário estabelecer um mecanismo de colaboração entre os objetos – por intermédio 
da troca de mensagens – para realizar a função maior. 
Quando o padrão Especialista na Informação não é seguido, é comum encontrar uma solução 
deficiente conhecida como God Class. Essa solução consiste em duas etapas: 
1. Etapa 1 
✓ Definir, nas classes de domínio, apenas operações de acesso aos seus atributos (operações 
conhecidas como getters e setters). 
2. Etapa 2 
✓ Concentrar a lógica de determinada funcionalidade do sistema em uma única classe 
(usualmente definida na forma de uma classe de controle ou de um serviço) na qual se 
encontram algoritmos complexos utilizando as operações de acesso das diversas classes de 
domínio, as quais, nesse estilo de solução, são meras fornecedoras de dados. 
Há, porém, situações em que a utilização desse padrão pode comprometer conceitos mais 
fundamentais, como, por exemplo, a coesão e o acoplamento. Uma delas ocorre quando existe algum 
aspecto tecnológico envolvido, como é o caso do armazenamento de dados ou da interface com usuário. 
Qual classe deveria ser responsável por implementar o armazenamento dos dados de um objeto da 
classe Pedido no banco de dados? 
Resposta 
Pelo padrão Especialista na Informação, deveria ser a própria classe Pedido, uma vezque ela possui as 
informações que serão armazenadas. Contudo, se implementarmos a solução de armazenamento na própria classe 
Pedido, a classe de negócio ficará acoplada com conceitos relativos à tecnologia de armazenamento (exemplos: SQL, 
NoSQL e arquivos). Isso fere o princípio fundamental da coesão, pois a classe Pedido ficaria sujeita a duas fontes de 
mudança: mudanças no negócio e na tecnologia de armazenamento utilizada, o que é claramente inadequado. 
O padrão Criador 
A instanciação de objetos é uma das instruções mais presentes em um programa orientado a objetos. 
Sempre que necessário, pode-se utilizar o operador new para criar um objeto em Java. Entretanto, a 
instanciação indiscriminada – e sem critérios bem definidos – de objetos em diferentes partes do código 
tende a gerar uma estrutura pouco flexível, difícil de modificar e com alto acoplamento entre os módulos. 
Quando se cria um objeto da classe B em um método da classe A por meio de um comando 
“new B()”, estabelece-se uma relação de dependência entre duas implementações. 
A dependência acontece porque, em Java, uma classe é uma implementação concreta de um 
conjunto de operações. Nesse exemplo, portanto, A é dependente de B. 
Em várias situações, entretanto, a criação de uma relação de dependência entre duas ou mais 
classes torna o sistema inflexível, dificultando a sua evolução. Deve-se estruturar um projeto para fazer com 
que as implementações dependam de abstrações, especialmente em casos nos quais um módulo depende 
de um serviço que pode ter diferentes implementações. Dessa forma, a pergunta que esse padrão tenta 
responder é: quem deve ser responsável pela instanciação de um objeto de determinada classe? 
Solução do Criador 
O padrão Criador recomenda atribuir a uma classe X a responsabilidade de criar uma instância da 
classe Y se uma ou mais das seguintes condições for(em) verdadeira(s): 
• X é um agregado formado por instâncias de Y. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 102 
 
• X contém instâncias de Y. 
• X registra instâncias de Y. 
• X possui os dados de inicialização necessários para a criação de uma instância de Y. 
No exemplo do sistema de vendas de produtos pela internet, considerando o modelo de classes 
ilustrado na figura 3, qual classe deveria ser a responsável por criar uma instância da classe ItemPedido? 
 
Uma abordagem comum – mas inadequada – é instanciar esse item em uma classe de serviço e 
apenas acumulá-lo no Pedido. Entretanto, quando se trata de um agregado, isto é, um objeto composto por 
vários itens, a responsabilidade pela criação dos itens, segundo o padrão Criador, deve ser alocada ao 
agregado, que é responsável por todo o ciclo de vida dos seus itens (criação e destruição). 
Veja no código adiante que a classe Pedido contém uma lista de itens. Essa lista implementa o 
relacionamento de composição entre Pedido e ItemPedido, só podendo ser modificada dentro da classe 
Pedido, pois ela controla o ciclo de vida dos seus itens. A criação de um novo item do pedido é realizada 
pela operação adicionarItem que recebe os parâmetros quantidade e produto. 
public class Pedido { 
 private int numero; 
 private java.util.Date data; 
 private List itens; 
 
 public void adicionarItem(int quantidade, Produto produto) { 
 itens.add(new ItemPedido(itens.size() + 1, quantidade, produto)); 
 } 
 public Iterator getItens() { 
 return itens.iterator(); 
 } 
 public BigDecimal obterValorTotal() { 
 BigDecimal resultado = new BigDecimal(0); 
 itens.forEach(item -> { 
 resultado.add(item.obterValor()); 
 }); 
 return resultado; 
 } 
 
 public int getNumero() { 
 return numero; 
 } 
 public void setNumero(int numero) { 
 this.numero = numero; 
 } 
 public Date getData() { 
 return data; 
 } 
 public void setData(Date data) { 
 this.data = data; 
 } 
 } 
Padrões de Projetos de Software com Java 
Marcio Quirino - 103 
 
O diagrama ilustrado na figura 4 apresenta a interação entre os objetos nessa solução. Um objeto de 
serviço, por exemplo, solicita ao objeto Pedido que adicione um novo item correspondente ao produto e à 
quantidade de itens passados como argumentos da mensagem adicionarItem. O objeto Pedido, por sua vez, 
utiliza esses argumentos para instanciar um novo ItemPedido, acrescentando-o a uma lista de itens que 
implementa o relacionamento entre o pedido e os seus itens. 
 
Consequências do Criador 
O padrão Criador é especialmente indicado para a criação de instâncias que formam parte de um 
agregado, pois o elemento que controla o ciclo de vida das suas partes é o próprio agregado, o qual, aliás, 
naturalmente já está relacionado com as suas partes. Esse padrão não é apropriado em algumas situações 
especiais, como é o caso da criação condicional de uma instância de uma família de classes similares. 
Exemplo 
Um sistema de vendas trabalha com diferentes soluções de pagamento. Por questões contratuais, a loja A 
opera com a solução de pagamento X; e a B, com a solução de pagamento Y. O problema passa a ser como criar a 
instância em conformidade com alguma parametrização externa feita para cada loja. Nesse caso, deve-se delegar a 
instanciação para uma classe auxiliar denominada Fábrica, aplicando o padrão de projeto GoF Abstract Factory. 
De forma geral, quando a instanciação de objetos envolver cenários mais complexos, como o 
compartilhamento de objetos para racionalizar o uso de memória ou a criação de uma instância de uma 
família de classes similares condicionada ao valor de alguma configuração externa, será mais adequado 
aplicar padrões de projeto específicos, como os padrões GoF Abstract Factory, Builder, Prototype ou Factory 
Method. 
2. Padrões Coesão Alta e Controlador 
O padrão Coesão Alta 
Coesão é um conceito que nos permite avaliar se as responsabilidades de um módulo estão 
fortemente relacionadas e possuem o mesmo propósito. O objetivo é criar módulos com coesão alta, ou 
seja, módulos que tenham um propósito bem definido. 
Módulos ou classes com coesão baixa realizam muitas operações pouco correlacionadas, gerando 
sistemas de difícil entendimento, reuso e manutenção, além de muito mais vulneráveis às mudanças. 
Portanto, a pergunta que esse padrão tenta responder é a seguinte: como definir as responsabilidades dos 
módulos de forma que a complexidade do sistema resultante seja gerenciável, facilitando o seu 
entendimento e futuras evoluções? 
Padrões de Projetos de Software com Java 
Marcio Quirino - 104 
 
Solução da Coesão Alta 
A solução proposta por esse padrão consiste em definir módulos de coesão alta. 
Como se mede a coesão de um módulo? 
O conceito de coesão está ligado ao critério utilizado para reunir um conjunto de elementos em um 
mesmo módulo. Note que esse conceito pode ser aplicado a módulos de diferentes níveis de granularidade: 
A. Coesão de um método de uma classe 
✓ Um método reúne um conjunto de instruções. Pode-se avaliar se essas instruções formam 
um método com um propósito bem definido. 
B. Coesão de uma classe 
✓ Uma classe reúne um conjunto de atributos e operações. Pode-se avaliar se esses atributos 
e essas operações formam uma classe com um propósito bem definido. 
C. Coesão de um pacote 
✓ Um pacote reúne um conjunto de classes e interfaces. Pode-se avaliar se essas classes 
formam um pacote com um propósito bem definido. 
D. Coesão de um subsistema 
✓ Um subsistema reúne um conjunto de pacotes. 
A coesão de um módulo, seja ele uma classe, um pacote ou um subsistema, pode ser classificada 
de acordo com o critério utilizado para reunir o conjunto dos elementos que o compõem. Vamos conheceragora esses critérios – do nível mais baixo para o mais alto de coesão. 
Coesão coincidente 
Quando os elementos estão agrupados em um módulo de forma arbitrária ou por conveniência, 
dizemos que esse módulo possui coesão coincidente. De forma esquemática, o exemplo mais à frente 
apresenta a classe Utils, que reúne operações, como a formatação de números, a conversão de medidas e 
o envio de arquivos via FTP. Esse módulo possui coesão baixa, pois muitas responsabilidades de diferentes 
naturezas estão reunidas em um único módulo. 
public class Utils { 
 public String formatarData (Date data) { 
 // implementação da formatação de data no formato DD/MM/AAAA 
 } 
 public double converterMetrosEmPolegadas (double metros) { 
 // implementação da conversão de metros em polegadas 
 } 
 public double converterPolegadasEmMetros (double polegadas) { 
 // implementação da conversão de polegadas em metros 
 } 
 public void enviarArquivoPorFTP (File arquivo, String endereço) { 
 // implementação do envio do arquivo por FTP 
 } 
 } 
Atenção 
O que normalmente motiva a criação desse tipo de módulo é a conveniência para os desenvolvedores de um 
projeto. O problema é que, se você quiser utilizar apenas uma dessas operações em um outro projeto, terá de trazer o 
módulo completo, incluindo todas as operações não utilizadas. Além disso, qualquer modificação feita em uma 
operação fará com que todo o módulo tenha de passar pelo ciclo de aprovação e de liberação. 
Coesão lógica 
Em um módulo com coesão lógica, os elementos são agrupados por estarem logicamente 
relacionados ou por realizarem funções diferentes, ainda que tendo a mesma natureza. O código adiante 
apresenta a estrutura de implementação de uma classe que reúne operações de leitura de dados de 
produtos a partir de: 
Padrões de Projetos de Software com Java 
Marcio Quirino - 105 
 
• Um arquivo texto local. 
• Um arquivo obtido via FTP. 
• Um banco de dados relacional. 
Esse é um exemplo de classe com coesão lógica, pois são reunidas em uma mesma classe todas 
as operações capazes de ler dados de produtos independentemente da fonte de dados disponível. Uma 
solução mais adequada seria separar a leitura de cada fonte em um módulo à parte. 
public class ProdutoRepository { 
 public static final int ARQUIVO_LOCAL = 1; 
 public static final int ARQUIVO_FTP = 2; 
 public static final int BANCO_RELACIONAL = 3; 
 
 public Produto obterProduto (int origem, String chave) { 
 switch (origem) { 
 case ARQUIVO_LOCAL: 
 return obterProdutoViaArquivoLocal (chave); 
 case ARQUIVO_FTP: 
 return obterProdutoViaArquivoFTP (chave); 
 case BANCO_RELACIONAL: 
 return obterProdutoViaBancoRelacional (chave); 
 } 
 } 
 private Produto obterProdutoViaArquivoLocal (String chave) { 
 // implementação da operação de leitura do Produto via arquivo local 
 } 
 private Produto obterProdutoViaArquivoFTP (String chave) { 
 // implementação da operação de leitura do Produto via arquivo FTP 
 } 
 private Produto obterProdutoViaBancoRelacional (String chave) { 
 // implementação da operação de leitura do Produto via banco relacional 
 } 
 } 
Coesão temporal 
Um módulo com coesão temporal é aquele em que os seus elementos são agrupados por serem 
executados em determinado instante do tempo. 
São colocadas no módulo Startup todas as operações executadas na inicialização do sistema, como, 
por exemplo: 
• Inicialização do log do sistema 
• Inicialização da interface gráfica com o usuário 
• Inicialização das conexões com banco de dados 
• Inicialização das tarefas de segundo plano 
Reúnem-se em um módulo, assim, as responsabilidades de diferentes naturezas por elas serem 
executadas em um momento específico, isto é, a inicialização do sistema. Uma solução mais adequada 
seria separar cada problema de inicialização (log, interface gráfica, banco de dados e assim por diante) em 
um módulo à parte. O código adiante ilustra um exemplo de módulo com coesão temporal. 
public class Startup { 
 public void inicializar() { 
 inicializarLog(); 
 inicializarGUI(); 
 inicializarConexoesBD(); 
 inicializarTarefasSegundoPlano(); 
 } 
 private void inicializarLog() { 
 // inicializar log do sistema 
 } 
 private void inicializarGUI() { 
 // inicializar interface gráfica com o usuário 
 } 
Padrões de Projetos de Software com Java 
Marcio Quirino - 106 
 
 private void inicializarConexoesBD() { 
 // inicializar conexões com banco de dados 
 } 
 private void inicializarTarefasSegundoPlano() { 
 // inicializar tarefas de segundo plano, executadas em threads específicas 
 } 
 } 
Coesão procedural 
Um módulo com coesão procedural é aquele cujos elementos são agrupados por eles serem 
executados em determinada sequência utilizando diferentes conjuntos de dados. Normalmente, uma classe 
com essa coesão corresponde à implementação de uma God Class, ou seja, ela recebe uma requisição de 
execução de um serviço da aplicação e concentra nela própria o código de processamento dessa requisição 
em vez de dividir essa responsabilidade em operações menores de outras classes. 
O código à frente apresenta um exemplo da estrutura de um módulo com coesão procedural. 
public class ServicoPedido { 
 public void confirmarPedido() { 
 obterDadosPagamento(); 
 validarDadosPagamento(); 
 aprovarPagamento(); 
 salvarPedido(); 
 atualizarEstoque(); 
 enviarPedidoParaProvisionamento(); 
 enviarEmailConfirmacao(); 
 } 
 private void obterDadosPagamento() { 
 // obter dados do cliente para o pagamento 
 } 
 private void validarDadosPagamento() { 
 // validar os dados para o pagamento 
 } 
 private void aprovarPagamento() { 
 // realizar o pagamento na forma escolhida 
 } 
 private void salvarPedido() { 
 // salvar o pedido no banco de dados 
 } 
 private void atualizarEstoque() { 
 // atualizar o estoque, reservando os itens do pedido 
 } 
 private void enviarPedidoParaProvisionamento() { 
 // enviar o pedido para o setor de logística para provisionamento 
 } 
 private void enviarEmailConfirmacao() { 
 // enviar email de confirmação do pedido para o cliente 
 } 
 } 
Nesse exemplo, a classe ServicoPedido reúne a implementação de todos os passos necessários 
para a realização do procedimento de confirmação do pedido, como, entre outros passos, a obtenção dos 
dados para pagamento, a aprovação do pagamento e o armazenamento do pedido no banco de dados. 
Atenção 
Perceba que a classe ServicoPedido trata de assuntos completamente diferentes – ainda que necessários – 
para a confirmação do pedido. Esse estilo de construção deve ser evitado, pois dá origem a módulos de complexidade 
muito alta e com pouca flexibilidade. 
Coesão de comunicação 
Em um módulo com coesão de comunicação, os elementos são agrupados por eles realizarem 
funções diferentes utilizando o mesmo conjunto de dados. O código a seguir ilustra o esquema de um módulo 
com essa coesão. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 107 
 
public class Conta { 
 public BigDecimal saldo() { 
 // obter o saldo da conta 
 } 
 public void enviarExtratoParaCliente(){ 
 // gerar relatório com extrato da conta e enviar por e-mail para o cliente 
 } 
 public void salvar() { 
 // salvar os dados do cliente no banco de dados 
 } 
} 
Observe que a classe Conta reúne operações de naturezas diversas, como obtenção do saldo, 
geração de extrato com envio para o cliente e armazenamento em banco de dados. O que elas têm em 
comum é o fato de trabalharem sobre o mesmo conjunto de dados, isto é, os dados da conta. 
Coesão sequencial 
Um módulo com coesão sequencial é aquele cujos elementos são agrupados por eles conterem 
todos os passos de execução de um procedimento sobre um mesmo conjunto original de dados, sendo que 
os resultados de um passo são utilizados como a entrada para o passo seguinte. A responsabilidade aqui é 
mais específica que a observada na coesão procedural, pois reúnem-se elementos que trabalham sobre o 
mesmo conjunto de dados, formando um pipeline de processamento. 
O código adiante contém o esquema de um módulo com coesão sequencial, pois o procedimento 
transforma um bilhete recebido em formato string em um registro desse bilhete armazenado no banco de 
dados. 
public class ServicoBilhetagem { 
 public void capturarBilhete(String dados) { 
 Bilhete bilheteFormatado = formatarBilhete(dados); 
 Bilhete bilheteIdentificado = identificarBilhete(bilheteFormatado); 
 salvarBilhete(bilheteIdentificado); 
 } 
 private Bilhete formatarBilhete(String dados) { 
 // formata o bilhete, extraindo os campos do texto recebido como parâmetro. 
 } 
 private Bilhete identificarBilhete(Bilhete bilhete) { 
 // gera o campo de identificação do bilhete baseado nos seus campos 
 } 
 private void salvarBilhete(Bilhete bilhete) { 
 // salvar os dados do cliente no banco de dados 
 } 
} 
Nesse exemplo, o procedimento é formado por três passos, sendo que o resultado de um passo é 
utilizado como a entrada para o passo seguinte. Além disso, os passos podem envolver o processamento 
de naturezas diversas. 
Coesão funcional 
Um módulo com coesão funcional reúne elementos que, juntos, cumprem um único propósito bem 
definido. As classes do pacote java.io da linguagem Java, por exemplo, estão nesse pacote pelo propósito 
de elas reunirem todas as responsabilidades de entrada e de saída. 
Nesse pacote, pode-se encontrar classes com responsabilidades bem específicas. Entre muitas 
outras, destacam-se: 
A. FileOutputStream 
✓ Para a escrita de arquivos binários. 
B. FileInputStream 
✓ Para a leitura desses arquivos. 
C. FileReader 
✓ Para a leitura de arquivos texto. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 108 
 
D. FileWriter 
✓ Para a escrita desses arquivos. 
Dessa forma, o padrão Coesão Alta propõe a construção de módulos com coesão funcional. Cada 
módulo, portanto, deve reunir elementos que contribuam para que um único propósito bem definido seja 
atingido. 
Consequências da Coesão Alta 
Coesão é um dos princípios fundamentais em projetos de software. A base da modularidade de um 
software está na definição de módulos com coesão alta e acoplamento baixo. Sistemas construídos com 
módulos que apresentem uma coesão alta tendem a: 
• Ser mais flexíveis. 
• Ser mais fáceis de se entender e de se evoluir. 
• Proporcionar maiores possibilidades de reutilização. 
• Facilitar a elaboração de soluções de acoplamento baixo (enquanto a tendência dos módulos 
de coesão baixa seja a de gerar soluções de acoplamento alto). 
No que tange à complexidade, os sistemas com coesão alta diferem dos sistemas com coesão baixa 
conforme vemos a seguir: 
1. Coesão alta 
✓ A complexidade está distribuída por vários módulos, cada um contribuindo para resolver um 
pedaço específico do problema. 
2. Coesão baixa 
✓ A complexidade fica centralizada em um pequeno número de módulos, os quais, muitas 
vezes, possuem centenas ou milhares de linhas de código. 
O padrão Controlador 
Um sistema interage com elementos externos, também conhecidos como atores. Muitos desses 
elementos geram eventos que devem ser capturados e processados, e gerar alguma resposta, seja ela 
interna ou externa. 
Exemplo 
Quando o cliente solicita o fechamento de um pedido em uma loja on-line, esse evento precisa ser capturado 
e processado pelo sistema de vendas dela. 
A quem devemos atribuir a responsabilidade de processar eventos que correspondam a 
requisições lógicas de execução de operações do sistema? 
Essa é a pergunta que o padrão Controlador tenta responder. 
Solução do Controlador 
O padrão Controlador recomenda que a responsabilidade de receber um evento de sistema e de 
coordenar a produção da sua resposta precisa ser alocada a uma classe que represente uma das seguintes 
opções: 
1. Opção 1 
✓ Uma classe correspondente ao sistema ou a um subsistema específico, solução também 
conhecida pelo nome Controlador Fachada. Essa solução é normalmente utilizada em 
sistemas com poucos eventos. 
2. Opção 2 
✓ Uma classe correspondente ao caso de uso em que o evento ocorre. Nesse caso, essa classe 
pode ter o seu nome formado pelo nome do caso de uso e por um prefixo, como Processador, 
Padrões de Projetos de Software com Java 
Marcio Quirino - 109 
 
Controlador, Serviço ou algo similar. Ela ainda deve reunir o tratamento de todos os eventos 
que o sistema receba no contexto desse caso de uso. Tal solução é indicada como alternativa 
a concentrar as responsabilidades de tratamento de eventos de diferentes naturezas em um 
único Controlador Fachada, evitando, assim, a criação de um controlador com coesão baixa. 
Um componente de interface captura os eventos de interface oriundos de teclado e mouse, por 
exemplo, e gera uma requisição para o controlador, que é o primeiro objeto, depois da camada de interface 
com o usuário, responsável por receber uma solicitação de sistema e coordenar a produção da respectiva 
resposta. 
O controlador não faz parte da interface com o usuário. 
Em uma implementação Java Desktop, as classes do Java Swing geram as solicitações, enquanto, 
em uma implementação com JSP, os servlets é que as geram. Em uma aplicação web rich client, por sua 
vez, o código Javascript da interface com o usuário gera as solicitações para o controlador, o qual, portanto, 
fornece uma interface de alto nível para as diferentes formas de interação do usuário com o sistema. 
Exemplo 
Em um sistema de internet banking, o usuário informa todos os dados de uma transferência de valores para 
uma conta destino; ao pressionar o botão “transferir”, o componente de interface com o usuário gera uma requisição 
para o controlador realizar o processamento lógico da transferência. Com isso, um mesmo controlador pode atender 
às solicitações realizadas por diferentes interfaces com o usuário (web, dispositivo móvel, totem 24 horas etc.). 
Veja no diagrama representado na figura 5 que, quando o usuário clica no botão “transferir”, após ter 
preenchido os dados da transferência, o componente UI (interface com o usuário) gera uma requisição para 
o controlador ServicoTransferencia, o qual, por sua vez, coordena a execução da transação interagindo com 
os objetos Conta e ContaRepository. 
 
Atenção 
O componente UI não realiza nenhuma operação ligada à lógica do negócio nem precisa conhecer os diversos 
componentes que compõem essa lógica. Ele, na verdade, apenas gera uma requisição para a classe controladora 
solicitando a execução da operação de sistema correspondente às interações realizadas pelo usuário com os 
componentes visuais. 
Consequências do Controlador 
Ao receber uma requisição, um módulo Controlador normalmente coordena e controla os elementos 
responsáveis pela produção da resposta. Imagine uma orquestra com um maestro e vários músicos. Ela 
conta com um maestro (controlador) que comanda o momento emque cada músico deve entrar em ação, 
mas ele mesmo não toca nenhum instrumento. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 110 
 
Da mesma forma, um módulo Controlador é o grande orquestrador de um conjunto de objetos – cada 
qual com sua responsabilidade específica na produção da resposta ao evento. Um problema que pode 
ocorrer com esse padrão é alocar ao Controlador responsabilidades além da orquestração, como se o 
maestro, além de comandar os músicos, também ficasse responsável por tocar o piano, a flauta, o violino e 
outros instrumentos. 
A concentração de responsabilidades reduz a coesão do controlador. 
Uma importante consequência da utilização desse padrão é que os componentes de interface com o 
usuário não devem assumir a responsabilidade do tratamento de eventos lógicos de sistema. Eles devem 
apenas capturar as ações do usuário na interface e traduzi-las em um evento lógico de sistema a ser tratado 
por algum Controlador. 
O Controlador Fachada (ou Controlador por Caso de Uso) corresponde à aplicação do padrão de 
projeto GoF Facade, pois essa classe de controle fornece uma interface de alto nível para a camada de 
interface com o usuário, isolando-a dos componentes internos da lógica do sistema. 
3. Padrões Acoplamento Baixo e Polimorfismo 
O padrão Acoplamento Baixo 
Um acoplamento corresponde ao grau de dependência de um módulo em relação a outros do 
sistema. Um módulo com acoplamento alto depende de vários outros módulos e tipicamente apresenta 
problemas, como, por exemplo: 
• Propagação de mudanças pelas relações de dependência, isto é, a mudança em um módulo 
causa um efeito cascata de mudanças nos módulos dependentes. 
• Dificuldade para entender um módulo isoladamente. 
• Dificuldade para reusar um módulo em outro contexto por exigir a presença dos diversos 
módulos que formam a sua cadeia de dependências. 
Portanto, quando você estiver dividindo as responsabilidades pelos módulos de um software, pense 
sempre nos impactos que uma mudança pode provocar. Nesse sentido, podemos diferenciar os sistemas 
conforme a seguir: 
A. Acoplamento baixo 
✓ As mudanças geram um impacto em poucas classes. 
B. Acoplamento alto 
✓ As mudanças criam um efeito dominó que impacta muitas classes. 
Atenção 
Outra questão importante em relação ao acoplamento é a natureza das dependências. Se uma classe A 
depende de uma classe B, diz-se que A depende da implementação concreta presente em B. Por outro lado, se uma 
classe A depende de uma interface I, é dito que A depende de uma abstração, uma vez que A poderia trabalhar com 
diferentes implementações concretas de I sem depender diretamente de nenhuma implementação específica. 
Em geral, sistemas mais flexíveis são construídos quando fazemos com que as implementações 
(classes) dependam de abstrações (interfaces). Isso é uma realidade especialmente nos casos em que a 
interface abstrai diferentes possibilidades de implementação, como: 
A. Envolver diferentes soluções tecnológicas 
✓ Como, por exemplo, as soluções de armazenamento e recuperação de dados. 
B. Contar com distintas questões de negócio 
✓ No caso, por exemplo, de diferentes regras de negócio e fornecedores de uma solução de 
pagamento on-line. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 111 
 
Um sistema orientado a objetos é composto por vários objetos com responsabilidades 
específicas e bem definidas. 
A complexidade do sistema emerge das relações de colaboração que estabelecemos entre os 
objetos. Quando projetamos um mecanismo de colaboração, naturalmente definimos relações de 
dependência e, portanto, geramos acoplamento entre os elementos envolvidos. Desse modo, o objetivo não 
é simplesmente minimizar dependências, e sim construir uma estrutura adequada e equilibrada delas. Cuida-
se especialmente da natureza das dependências estabelecidas, ou seja, é dada uma preferência à presença 
das dependências em relação às abstrações em detrimento das dependências quanto às implementações 
específicas. 
A pergunta que esse padrão tenta responder, portanto, é esta: como definir relações de dependência 
entre as classes de um sistema de forma a manter o acoplamento baixo, minimizar o impacto de mudanças 
e facilitar o reuso? 
Solução do Acoplamento Baixo 
A solução proposta por esse padrão consiste em distribuir as responsabilidades a fim de gerar um 
acoplamento baixo entre os módulos. 
Você sabe como se mede o grau de acoplamento entre os módulos? 
O grau de acoplamento está relacionado à forma com que uma relação de dependência é 
estabelecida entre dois módulos. A seguir, conheceremos tais formas do nível mais alto para o mais baixo 
de acoplamento. 
Acoplamento de conteúdo 
Ele ocorre quando um módulo utiliza aspectos de implementação de outro, ferindo o princípio a 
estabelecer que um módulo deve ocultar dos demais suas decisões de implementação, de forma que seja 
possível alterá-las sem se preocupar com os possíveis efeitos em outros módulos. 
Veremos um exemplo de acoplamento de conteúdo. No código a seguir, a classe Data define seus 
atributos “dia”, “mês” e “ano” como públicos, enquanto a classe ModuloCliente acessa diretamente o atributo 
“mês” de uma instância de Data. Trata-se de um acoplamento de conteúdo, pois a forma de armazenamento 
da informação de Data está exposta e é diretamente utilizada pelos demais módulos. 
public class Data { 
 public int dia; 
 public int mes; 
 public int ano; 
 
 public int getDiaSemana() { 
 // implementação para retornar o dia da semana correspondente à data 
 } 
 public adicionaDias(int dias) { 
 // implementação para adicionar x dias à data 
 } 
} 
 
public class ModuloCliente { 
 public void metodo(Data data) { 
 if (data.mes == 8) { 
 System.out.println(“mês de agosto”); 
 } 
 } 
} 
Classe Data e class ModuloCliente. 
Você consegue visualizar o que aconteceria com os módulos que fazem o mesmo tipo de uso da classe 
Data caso resolvêssemos mudar a representação da data para texto ou para o número de dias julianos? 
Padrões de Projetos de Software com Java 
Marcio Quirino - 112 
 
Acoplamento global 
O acoplamento global entre dois módulos ocorre quando eles se comunicam por intermédio de 
recursos, como, por exemplo, variáveis globais. Ao contrário de C++, o Java não possui uma sintaxe para a 
definição de variáveis globais, mas é possível atingir um efeito similar ao se declarar em uma classe um 
atributo com os modificadores public static. 
O código adiante exibe um exemplo de acoplamento global. A classe Globais define um conjunto de 
atributos que pode ser acessado globalmente. 
public class Globais { 
 public static int limiteParaSaque = 500; 
 public static String nomeBanco; 
} 
 
public class ModuloA { 
 public void operacaoA() { 
 if (valorno qual existem os 
módulos de Vendas e Estoque. Os dois módulos precisam acessar e atualizar dados 
referentes aos produtos vendidos na loja e, para isso, ambos utilizam a tabela Produto em 
um banco de dados relacional. Dessa forma, pode-se dizer que existe um acoplamento 
externo entre ambos. Você consegue visualizar esse acoplamento? 
B. Segunda situação 
✓ Suponha agora que você seja o responsável pelo módulo de Vendas e que outro 
desenvolvedor, responsável pelo de Estoque, resolva mudar a tabela Produto, criando alguns 
campos e modificando outros existentes. Essas mudanças podem trazer impactos para o 
módulo Vendas? Possivelmente sim, não é mesmo? Esses impactos são a evidência do 
acoplamento resultante do uso de recursos externos compartilhados. 
Acoplamento de controle 
Ocorre quando um módulo controla a lógica interna de outro por meio da passagem de alguma 
informação de controle. O código a seguir ilustra um exemplo de acoplamento de controle. 
A operação obterProduto da classe ProdutoRepository pode recuperar dados de um produto a partir 
de um arquivo local, de um arquivo via FTP ou de um banco de dados relacional. Já a classe ModuloCliente 
Padrões de Projetos de Software com Java 
Marcio Quirino - 113 
 
chama a operação obterProduto, passando uma informação de controle que indica de onde deve ser feita a 
leitura. 
public class ProdutoRepository { 
 public static final int ARQUIVO_LOCAL = 1; 
 public static final int ARQUIVO_FTP = 2; 
 public static final int BANCO_RELACIONAL = 3; 
 
 public Produto obterProduto(int origem, String chave) { 
 switch (origem) { 
 case ARQUIVO_LOCAL: 
 return obterProdutoViaArquivoLocal (chave); 
 case ARQUIVO_FTP: 
 return obterProdutoViaArquivoFTP (chave); 
 case BANCO_RELACIONAL: 
 return obterProdutoViaBancoRelacional (chave); 
 } 
 } 
 private Produto obterProdutoViaArquivoLocal (String chave) { 
 // implementação da operação de leitura do Produto via arquivo local 
 } 
 private Produto obterProdutoViaArquivoFTP (String chave) { 
 // implementação da operação de leitura do Produto via arquivo FTP 
 } 
 private Produto obterProdutoViaBancoRelacional (String chave) { 
 // implementação da operação de leitura do Produto via banco relacional 
 } 
} 
 
public class ModuloCliente { 
 public void operacaoCliente(ProdutoRepository repositorio) { 
Produto p = repositorio.obterProduto (ProdutoRepository.BANCO_RELACIONAL, 
“1234”); 
 // restante da implementação da operação viria aqui 
 } 
} 
Classe ProdutoRepository e class ModuloCliente. 
Observe que o módulo cliente não apenas está exposto a todas as implementações do módulo de 
leitura de dados do produto, como também controla esse módulo, indicando a implementação desejada. 
Uma solução menos acoplada teria o módulo cliente dependendo de uma abstração, ou seja, de um serviço 
capaz de obter um produto, sem que ele precise saber que existem diferentes implementações para esse 
serviço. 
Acoplamento de estrutura 
Ele acontece quando um módulo chamador passa uma estrutura de dados para um módulo chamado, 
o qual, por sua vez, utiliza apenas um pequeno subconjunto de dados dessa estrutura. O código à frente 
ilustra um exemplo de acoplamento de estrutura. 
A classe Pedido contém todas as informações de um pedido. Já a classe CalculadoraFrete possui 
uma operação que calcula o frete de um pedido recebido como parâmetro. Entretanto, o algoritmo de cálculo 
do frete utiliza apenas o endereço de destino, o que configura um exemplo de utilização de uma pequena 
parcela (endereço de destino) de uma estrutura de dados (Pedido). 
public class Pedido { 
 private Date data; 
 private int local; 
 private List itens; 
 private Cliente cliente; 
 private Endereco enderecoEntrega; 
 private Pagamento; 
 
 // restante da classe Pedido 
} 
Padrões de Projetos de Software com Java 
Marcio Quirino - 114 
 
 
public class CalculadoraFrete { 
 public BigDecimal obterValorFrete(Pedido pedido) { 
 // algoritmo de cálculo do valor de frete é feito utilizando apenas 
 // o endereço de destino do pedido 
 } 
} 
 
public class Exemplo { 
 CalculadoraFrete calculadoraFrete; 
 
 public void fecharPedido(Pedido pedido) { 
 calculadoraFrete.obterValorFrete(pedido); 
 } 
} 
Classe Pedido, classe CalculadoraFrete e classe Exemplo. 
Acoplamento de dados 
O acoplamento de dados ocorre quando um módulo se comunica com outro, passando apenas os 
dados dos quais o módulo chamado precisa para cumprir a sua responsabilidade. Nesse tipo de 
acoplamento, os módulos são independentes e se comunicam por meio de dados. 
O código adiante mostra um exemplo de acoplamento de dados no qual o módulo CalculadoraFrete 
recebe o endereço de destino, que é a informação necessária para calcular o frete. 
public class Pedido { 
 private Date data; 
 private int local; 
 private List itens; 
 private Cliente cliente; 
 private Endereco enderecoEntrega; 
 private Pagamento pagamento; 
 
 // restante da classe Pedido 
} 
 
public class CalculadoraFrete { 
 
 public BigDecimal obterValorFrete(Endereco enderecoDestino) { 
 // algoritmo de cálculo do valor de frete é feito utilizando apenas 
 // o endereço de destino do pedido 
 } 
} 
 
public class Exemplo { 
 CalculadoraFrete calculadoraFrete; 
 
 public void fecharPedido(Pedido pedido) { 
 calculadoraFrete.obterValorFrete(pedido.getEnderecoDestino()); 
 } 
} 
Classe Pedido, classe CalculadoraFrete e classe Exemplo. 
Portanto, os níveis de acoplamento, do mais alto para o mais baixo, são: 
• Conteúdo; 
• Global; 
• Externo; 
• Controle; 
• Estrutura; 
• Dados. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 115 
 
Ao dividir as responsabilidades pelos módulos, devem ser balanceadas três heurísticas ligadas às 
relações de dependência entre eles: 
A. Heurística 1 
✓ Minimizar o número de dependências entre os módulos sem comprometer a coesão, o que 
significa dizer que dependências desnecessárias não devem ser criadas. 
B. Heurística 2 
✓ Utilizar, sempre que possível, os níveis mais baixos de acoplamento, preferencialmente o 
acoplamento de dados. 
C. Heurística 3 
✓ Fazer com que as implementações dependam das abstrações, especialmente nas relações 
de dependência entre os módulos que implementam a lógica de negócio e aqueles 
relacionados à tecnologia, como, por exemplo, interface gráfica com o usuário, 
armazenamento de dados e integração com componentes externos. 
Consequências do Acoplamento Baixo 
O acoplamento é um princípio fundamental da estruturação de software. Ele, por isso, precisa ser 
considerado em qualquer decisão de projeto de software. 
Você sabe como os acoplamentos são criados concretamente em um programa? 
Em linguagens como Java, por exemplo, um acoplamento direto entre o módulo X e o módulo Y é 
criado quando: 
1. Y é utilizado como o tipo de um atributo definido em X. 
2. Um método definido em X invoca operações definidas em Y (operações com escopo de 
classe). 
3. Um método definido em X utiliza uma instância de Y, seja ela criada localmente no método, 
recebida como parâmetro do método ou obtida com o retorno de uma operação invocada 
dentro desse método. 
4. Uma instância de Y é criada dentro de X via inicialização ou dentro de algum método 
específico de X. 
5. X é descendente(herda direta ou indiretamente) de Y. 
6. Y é uma interface, e X a implementa. 
Além disso, os acoplamentos indiretos podem ser definidos pelo compartilhamento de recursos, 
como variáveis globais, arquivos e bancos de dados, por exemplo. Sempre que você utilizar uma dessas 
construções, um acoplamento será definido. 
Sendo assim, avalie com cuidado se esse acoplamento é realmente necessário ou se há alternativas 
que levariam a um menor acoplamento. Observe ainda se você pode criar alguma abstração para não gerar 
uma dependência de uma implementação específica. 
Atenção 
Em geral, manter as classes de domínio isoladas e não dependentes de tecnologia – de armazenamento, de 
interface com usuário ou de integração entre sistemas, entre outras opções – constitui uma política geral de 
acoplamento que deve ser seguida e que está presente em proposições, como, por exemplo, a arquitetura hexagonal 
ou a limpa. 
O padrão Polimorfismo 
Suponha que você esteja implementando os requisitos de pagamento em cartão de uma loja on-line. 
Para implementar um pagamento em cartão que interaja diretamente com uma administradora de cartão, é 
preciso passar por um longo e complexo processo de homologação perante a administradora. Imagine então 
realizar esse processo com cada uma! 
Padrões de Projetos de Software com Java 
Marcio Quirino - 116 
 
Comentário 
Atualmente existem diferentes brokers de pagamento homologados perante as diversas administradoras que 
fornecem uma API para a integração com sistemas de terceiros. No entanto, cada broker tem uma política de preços; 
eventualmente, podem surgir novos brokers com políticas mais atrativas no mercado. 
Agora imagine que você seja o responsável por fornecer uma solução de sistema para diferentes 
lojistas que podem escolher os brokers de pagamento em função das suas exigências de segurança, preço 
e volume de transações, entre outros fatores de influência. Isso significa que o nosso software tem de ser 
capaz de funcionar com diferentes brokers, cada um com a sua API proprietária. 
Quem não conhece polimorfismo resolveria esse problema com uma solução baseada em if-then-
else ou switch-case, sendo cada alternativa de broker mapeada em um case no switch ou em um else no if-
then-else, como ilustra o trecho de código a seguir. Pense como ficaria tal código se houvesse vinte brokers 
diferentes! 
public class FechamentoPedido { 
 private ConfiguracaoSistema config; 
 
 public FechamentoPedido(ConfiguracaoSistema config) { 
 this.config = config; 
 } 
 
 public BigDecimal fecharPedido(Pedido pedido) { 
 if (config.brokerPagamento == ConfiguracaoSistema.BROKER_1) { 
 config.broker1.efetuarPagamento_Broker1(); 
 else if (config.brokerPagamento == ConfiguracaoSistema.BROKER_2) { 
 config.broker2.efetuarPagamento_Broker2(); 
 else if (config.brokerPagamento == ConfiguracaoSistema.BROKER_3) { 
 config.broker3.efetuarPagamento_Broker3(); 
 } 
 } 
} 
Classe FechamentoPedido. 
Esse problema possui uma complicação adicional, já que cada broker fornece uma API proprietária 
com interfaces diferentes. Os sufixos Broker1, Broker2 e Broker3 nos nomes das operações representam o 
fato de que as operações de cada API não possuem o mesmo nome e que também podem diferir nos 
argumentos passados na chamada de cada operação. Em resumo, o padrão Polimorfismo procura resolver 
os seguintes problemas: 
1. Como produzir uma solução genérica para alternativas baseadas no tipo de um elemento 
criando módulos plugáveis? 
2. Como evitar construções condicionais complexas contendo um código condicional baseado 
no tipo do objeto ou em alguma propriedade específica? 
Comentário 
No nosso exemplo, as alternativas baseadas no tipo de um elemento correspondem aos diferentes tipos de 
brokers com as suas APIs proprietárias. 
Solução do Polimorfismo 
Você percebeu como a solução baseada em estruturas condicionais do tipo if-then-else ou switch-
case, além de ser mais complexa, cria um acoplamento do módulo chamador com cada implementação 
específica? Note como a classe FechamentoPedido depende diretamente de todas as implementações de 
broker de pagamento. Você se lembra de que um princípio geral de bons projetos orientados a objetos é 
haver implementações dependentes de abstrações, especialmente em casos nos quais essas abstrações 
possam ter várias implementações? 
Padrões de Projetos de Software com Java 
Marcio Quirino - 117 
 
A solução via polimorfismo consiste em criar uma interface genérica para a qual podem existir 
diversas implementações específicas. 
A estrutura condicional é substituída por uma única chamada, a qual, aliás, é feita utilizando essa 
interface genérica. O chamador, portanto, não precisa conhecer a classe que está do outro lado da interface 
provendo a implementação. A capacidade de um elemento (a interface genérica) poder assumir diferentes 
formas concretas (broker1, broker2 ou broker3, no exemplo anterior) é conhecida como polimorfismo. 
O código adiante ilustra a estrutura de solução para o exemplo do fechamento do pedido com a 
utilização do padrão Polimorfismo. 
public interface BrokerPagamento { 
 void efetuarPagamento(Pedido pedido); 
} 
 
public class Broker_1_Adapter implements BrokerPagamento { 
 private Broker1 broker1 = new Broker1(); 
 public void efetuarPagamento(Pedido pedido) { 
 // código específico para utilização do broker 1 
 broker1.efetuarPagamento_Broker1(); 
 } 
} 
 
public class Broker_2_Adapter implements BrokerPagamento { 
 private Broker1 broker2 = new Broker1(); 
 public void efetuarPagamento(Pedido pedido) { 
 // código específico para utilização do broker 2 
 broker2.efetuarPagamento_Broker2(); 
 } 
} 
 
public class FechamentoPedido { 
 private ConfiguracaoSistema config; 
 
 public class FechamentoPedido (ConfiguracaoSistema config) { 
 this.config = config; 
 } 
 public BigDecimal fecharPedido(Pedido pedido) { 
 config.broker.efetuarPagamento(); // chamada polimórfica 
 } 
} 
 
 
Public class Exemplo { 
 public void executar(Pedido pedido) { 
 ConfiguracaoSistema config = new ConfiguracaoSistema(); 
 config.setBroker(new Broker_1_Adapter()); 
 FechamentoPedido fechamento = new FechamentoPedido(config); 
 Fechamento.fecharPedido(pedido); 
 } 
} 
Fechamento do pedido com a utilização do padrão Polimorfismo. 
Vamos analisar o código: 
1. Criação da interface BrokerPagamento 
✓ Em vez de a classe FechamentoPedido utilizar diretamente todos os brokers, é criada uma 
interface BrokerPagamento para separar o módulo cliente dos módulos que implementam 
essa interface. 
2. Criação de adaptadores 
✓ Como cada broker possui uma interface específica, é criado um conjunto de adaptadores que 
implementa a interface genérica BrokerPagamento e faz a conversão entre a interface 
genérica e a específica de cada broker. Desse modo, um adaptador de interface é criado para 
cada broker de pagamento. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 118 
 
3. Implementação da operação efetuarPagamento 
✓ Verifique como a classe Broker_1_Adapter implementa a operação efetuarPagamento 
chamando uma operação específica da API do broker 1. De forma análoga, a classe 
Broker_2_Adapter opera com a API do broker 2. Observe ainda como o código condicional 
anteriormente presente na implementação da classe FecharPedido foi substituído por uma 
simples chamada à operação efetuarPagamento de um objeto que implementa a interface 
BrokerPagamento. 
Dica 
Como esse objeto pode assumir múltiplas formas112 
Acoplamento externo .......................................................................................................... 112 
Acoplamento de controle..................................................................................................... 112 
Acoplamento de estrutura ................................................................................................... 113 
Acoplamento de dados........................................................................................................ 114 
Consequências do Acoplamento Baixo .............................................................................................. 115 
O padrão Polimorfismo ........................................................................................................................... 115 
Solução do Polimorfismo .................................................................................................................... 116 
Consequências do Polimorfismo ........................................................................................................ 118 
4. Padrões Invenção Pura, Indireção e Variações Protegidas................................................ 118 
O padrão Invenção Pura ........................................................................................................................ 118 
Solução da Invenção .......................................................................................................................... 119 
Consequências da Invenção Pura ...................................................................................................... 120 
O padrão Indireção ................................................................................................................................. 120 
Solução da Indireção .......................................................................................................................... 121 
Consequências da Indireção .............................................................................................................. 122 
O padrão Variações Protegidas ............................................................................................................. 122 
Solução das Variações Protegidas ..................................................................................................... 123 
Consequências das Variações Protegidas ......................................................................................... 125 
Considerações finais .............................................................................................................................. 125 
Explore + ................................................................................................................................................ 125 
Referências ............................................................................................................................................ 126 
Tecnologias JPA e JEE ..................................................................................................................... 127 
Introdução .............................................................................................................................................. 127 
1. JPA no desenvolvimento web .............................................................................................. 127 
Mapeamento objeto-relacional ............................................................................................................... 127 
ORM (mapeamento objeto-relacional)................................................................................................ 127 
Entity Beans ....................................................................................................................................... 128 
Hibernate ............................................................................................................................................ 128 
Java Persistence API ............................................................................................................................. 129 
Definindo uma entidade JPA .............................................................................................................. 129 
Consulta e manipulação de dados ......................................................................................................... 131 
Padrões de Projetos de Software com Java 
Marcio Quirino - 6 
 
Entity Manager ................................................................................................................................... 131 
Inclusão de dados .............................................................................................................................. 132 
Exclusão de dados ............................................................................................................................. 132 
Execução do aplicativo ........................................................................................................................... 133 
Adicionando a biblioteca JDBC e o framework EclipseLink................................................................ 133 
Criando o banco de dados Derby ....................................................................................................... 135 
Resultado da execução do aplicativo ................................................................................................. 136 
Manipulando dados com NamedQueries ............................................................................................... 136 
Roteiro de prática ............................................................................................................................... 137 
2. Implementação de regras de negócio com EJBs ................................................................ 137 
Enterprise Java Beans (EJB) ................................................................................................................. 137 
Session beans ........................................................................................................................................ 139 
Stateless e stateful ............................................................................................................................. 139 
Interface de acesso ............................................................................................................................ 140 
Singleton ............................................................................................................................................ 140 
Session bean com Servlet .................................................................................................................. 140 
Message-driven beans (MDB) ................................................................................................................ 141 
Mensagerias ....................................................................................................................................... 141 
Modelo publish/subscribe .................................................................................................... 141 
Modelo point to point ........................................................................................................... 141 
MDB (message-driven bean) .............................................................................................................. 142 
Aplicativo corporativo ............................................................................................................................. 144 
EJBs ................................................................................................................................................... 144 
MDBs..................................................................................................................................................em tempo de execução, pois ele pode ser a instância de 
qualquer um dos adaptadores para os brokers específicos, dizemos que essa chamada é polimórfica. 
Você percebeu que o polimorfismo permite adicionar novas soluções de broker sem que haja a 
necessidade de alterar o módulo chamador (FechamentoPedido)? Basta, para tal, implementar um novo 
adaptador e adicioná-lo à configuração do sistema, pois o restante do sistema já está preparado para 
trabalhar com esse novo broker. 
Consequências do Polimorfismo 
O Polimorfismo é um princípio fundamental em projetos de software orientados a objetos que nos 
ajuda a resolver, de forma sintética, elegante e flexível, o problema de se lidar com variantes de 
implementação de uma mesma operação conceitual. O conceito dele está presente na definição de diversos 
padrões GoF. Listaremos alguns deles a seguir: 
• Adapter; 
• Command; 
• Composite; 
• Proxy; 
• State; 
• Strategy; 
Entretanto, é preciso ter cuidado para não construir estruturas genéricas para situações nas quais 
não haja uma possibilidade de variação. Uma solução genérica se mostra mais flexível, mas é preciso estar 
atento a fim de não investir um esforço na produção de soluções genéricas para os problemas que sejam 
específicos por natureza, ou seja, que não apresentem variantes de implementação. 
4. Padrões Invenção Pura, Indireção e Variações Protegidas 
O padrão Invenção Pura 
Você está desenvolvendo um sistema de vendas para uma loja on-line e identificou algumas classes 
que correspondem aos conceitos do negócio, tais como Produto, Cliente e Pedido. O padrão Especialista 
recomenda que uma responsabilidade seja atribuída ao módulo que possua o conhecimento necessário 
para realizá-la. Vimos que o cálculo do valor total do Pedido envolve o conhecimento de todos os itens e os 
seus respectivos valores. Ele, assim, deveria ser responsabilidade da classe Pedido. 
Qual classe deveria ser a responsável por salvar os dados do Pedido em um banco de dados 
relacional? 
Seguindo o padrão Especialista, deveríamos alocar essa responsabilidade na classe detentora das 
informações, que é a própria classe Pedido. Contudo, essa solução comprometeria o padrão Coesão Alta, 
já que alocaríamos em um mesmo módulo responsabilidades de diferentes naturezas, ou seja, a lógica de 
negócio e de armazenamento em banco de dados relacional. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 119 
 
Solução da Invenção 
A solução proposta pelo padrão Invenção Pura diz respeito à criação de classes artificiais, isto é, 
classes que não representam um conceito do domínio do problema, para gerar soluções com coesão alta e 
acoplamento baixo. O problema do armazenamento do Pedido em um banco de dados pode ser 
implementado em uma classe PedidoRepository, a qual, por sua vez, fica responsável pelas operações 
básicas com pedidos em um meio de armazenamento como, por exemplo, armazenar um novo pedido, 
atualizar os dados de um já existente, remover um pedido e recuperar um ou mais pedidos. 
O código a seguir ilustra a implementação da classe Pedido com os atributos e as responsabilidades 
ligados ao domínio do negócio e à classe PedidoRepository, que é uma “Invenção Pura”, definida com a 
responsabilidade de armazenar e recuperar pedidos de um banco de dados relacional. Com isso, é gerada 
uma solução com módulos de coesão alta, já que as funcionalidades de diferentes propósitos estão 
segregadas em classes distintas. 
public class Pedido { 
 private int numero; 
 private java.util.Date data; 
 private List itens; 
 
 public BigDecimal obterValorTotal() { 
 BigDecimal resultado = new BigDecimal(0); 
 itens.forEach(item -> { 
 resultado.add(item.obterValor()); 
 }); 
 return resultado; 
 } 
 public int getNumero() { 
 return numero; 
 } 
 public void setNumero(int numero) { 
 this.numero = numero; 
 } 
 public Date getData() { 
 return data; 
 } 
 public void setData(Date data) { 
 this.data = data; 
 } 
 public Iterator getItens() { 
 return itens.iterator(); 
 } 
 public void adicionarItem(int quantidade, Produto produto) { 
 itens.add(new ItemPedido(itens.size() + 1, quantidade, produto)); 
 } 
} 
 
public class PedidoRepository { 
 public void inserirPedido(Pedido pedido) { 
 // monta e executa SQL INSERT com os dados do pedido 
 } 
 public void atualizarPedido(Pedido pedido) { 
 // monta e executa SQL UPDATE dos dados do pedido 
 } 
 public void excluirPedido(Pedido pedido) { 
 // monta e executa SQL DELETE do registro do pedido 
 } 
 public Pedido recuperarPedidoPorNumero(int numero) { 
 // monta e executa SQL SELECT dos dados do pedido 
 // monta objeto Pedido a partir dos dados recuperados do banco de dados 
 // retorna objeto Pedido 
 } 
} 
Classe Pedido. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 120 
 
Consequências da Invenção Pura 
Esse padrão permite a criação de soluções com módulos de coesão alta e acoplamento baixo, sendo 
particularmente útil nas situações em que a heurística padrão para a aplicação do padrão Especialista na 
Informação gera módulos de coesão baixa. 
A criação de classes correspondentes a conceitos e elementos concretos encontrados no domínio 
do negócio é um princípio natural de projetos orientados a objetos. Essa estratégia é chamada de 
decomposição por representação. Já a criação de classes por Invenção Pura resulta da estratégia chamada 
decomposição por comportamento. Vamos entender a diferença: 
1. Decomposição por representação 
✓ Essa estratégia visa produzir módulos que representem os elementos do domínio sem uma 
transformação abruta do universo real para a sua representação em software. 
2. Decomposição por comportamento 
✓ Essa estratégia é aplicada nas situações em que se desejar agrupar comportamentos ou 
algoritmos sem necessariamente vinculá-los diretamente a classes que representam os 
conceitos do domínio do negócio. 
Exemplo 
A loja on-line pode ter diferentes políticas de desconto para pedidos dependendo da época do ano, do total do 
pedido, do endereço de entrega ou da frequência de compras do cliente. Em vez de colocar todos esses algoritmos na 
classe Pedido, que é a detentora primária das informações do pedido, separam-se essas políticas em uma família de 
classes correspondente aos diversos algoritmos de cálculo de desconto aplicando o padrão Invenção Pura. Como 
consequência, pode-se evoluir os algoritmos de desconto de forma independente da classe Pedido, gerando módulos 
com coesão mais alta e uma solução mais flexível. 
É importante equilibrar as duas estratégias (decomposição por representação e por comportamento). 
Quando a estratégia de decomposição por representação está sendo subutilizada, surgem sinais, como, por 
exemplo, classes do domínio do problema contendo apenas atributos e operações de acesso (get/set) ou 
algumas classes concentrando a lógica de negócio em muitos métodos com dezenas ou centenas de linhas 
de código. 
Padrões GoF, como o Adapter, o Strategy e o Command, são alguns exemplos de aplicação do 
padrão Invenção Pura, pois eles são baseados na decomposição de responsabilidades por comportamento. 
O padrão Indireção 
Indireção é uma das técnicas mais simples e mais utilizadas em projetos de software. Ela pode ser 
utilizada em diversas situações. 
Veja a seguir alguns problemas que podem ser solucionados pelo padrão Indireção: 
1. Comunicação entre objetos remotos 
✓ Um objeto A precisa chamar uma operação de um objeto remoto B. Como fazercom que A 
não precise lidar com questões, como sockets, serialização da mensagem e interpretação 
dos dados de retorno, entre outros aspectos inerentes à comunicação em rede? 
2. Utilização de API de terceiros 
✓ Um objeto A deve chamar uma operação de um serviço B cujo acesso é fornecido por um 
fornecedor terceiro. Por isso, não é possível modificar o código da API de acesso ao serviço 
B. Imagine que eventualmente você tenha de trabalhar com outros fornecedores, cada qual 
com a sua API proprietária. Como isolar o objeto A das diferenças entre as APIs dos diversos 
fornecedores? 
3. Abstração de tecnologia 
✓ Você precisa conectar diversos componentes clientes a uma camada de serviços que pode 
ser implementada em diferentes tecnologias, como, por exemplo, EJB, Web Services e REST 
Padrões de Projetos de Software com Java 
Marcio Quirino - 121 
 
API. Como fazer para que os clientes não precisem ser alterados em função de uma mudança 
na tecnologia de implementação da camada de serviços? 
4. Redução de acoplamento 
✓ Um objeto A que captura uma interação do usuário com o sistema tem de interagir com 
diversos objetos de negócio para cumprir sua função. Como reduzir o acoplamento de A com 
os diversos objetos da camada de negócio, tornando a implementação desse objeto menos 
complexa? 
Solução da Indireção 
A solução proposta pelo padrão Indireção consiste em substituir a conexão direta entre dois ou mais 
objetos por uma estrutura de comunicação mediada por um objeto intermediário. Assim, se um objeto A 
enviar uma mensagem diretamente para B, ele passará a mandá-la para um objeto intermediário X, o qual, 
por sua vez, fica responsável pela comunicação com B. 
Observemos como o padrão Indireção pode ser utilizado nas situações descritas anteriormente: 
1. Comunicação entre objetos remotos 
✓ Para isolar um objeto A cliente das complexidades de interação com um objeto remoto B, 
pode-se utilizar o padrão Gof Proxy, que consiste em definir um par de objetos (proxy e stub) 
entre os objetos A e B. O objeto proxy roda no mesmo processo de A oferecendo as mesmas 
operações de B, permitindo que A possa chamar operações de B como se eles estivessem 
rodando no mesmo processo. Já o objeto stub roda no mesmo processo de B, recebendo as 
requisições do proxy e repassando-as para o objeto B. Dessa forma, os objetos proxy e stub 
cuidam dos aspectos da comunicação remota, reduzindo a complexidade da implementação 
dos módulos A e B. 
2. Utilização de API de terceiros 
✓ Quando um objeto A depende de um serviço que pode ter diversas implementações 
fornecidas por diferentes terceiros, deve-se segregar A desses fornecedores por intermédio 
da inserção de objetos adaptadores. Esses objetos traduzem uma interface comum 
conhecida por A nas chamadas proprietárias fornecidas por cada API. Essa é a solução 
proposta pelo padrão GoF Adapter. 
3. Abstração de tecnologia 
✓ Padrões J2EE, como Service Locator e Business Delegate, são exemplos de solução para o 
problema da abstração de tecnologia. Eles encapsulam a forma com que um serviço é 
localizado e a tecnologia utilizada na sua implementação (exemplos: EJB, web service e 
REST API), fazendo com que todos os objetos clientes desse serviço se comuniquem com 
um objeto intermediário (Business Delegate) de forma independente de tecnologia. Esse 
objeto delega a chamada para o objeto real utilizando a tecnologia específica de 
comunicação. Assim, quando essa tecnologia mudar, basta alterar o Business Delegate, pois 
os clientes estão isolados do impacto dessa mudança. 
4. Redução de acoplamento 
✓ Dois padrões GoF (Facade e Mediator) têm o propósito de reduzir o acoplamento entre 
objetos. O padrão Facade oferece uma interface de alto nível para clientes de um subsistema, 
reduzindo o número de objetos com os quais eles precisam interagir. Já o padrão Mediator 
reduz uma rede de dependências entre objetos do tipo N x N para uma topologia 1 x N, 
substituindo as comunicações diretas entre os objetos por aquelas mediadas por um objeto 
intermediário responsável por receber as notificações dos objetos e encaminhar o 
processamento para os objetos correspondentes. 
Você conseguiu perceber como diversos padrões de projeto GoF são aplicações específicas 
do padrão Indireção? 
Padrões de Projetos de Software com Java 
Marcio Quirino - 122 
 
Consequências da Indireção 
Indireção é um princípio fundamental muito utilizado em padrões de projeto. 
Trata-se de introduzir uma camada entre o cliente e o fornecedor a fim de desacoplá-los, 
promovendo soluções mais flexíveis, reusáveis e mais fáceis de estender e evoluir. 
O padrão Indireção permite: 
1. Isolar o cliente das tecnologias específicas utilizadas pelo fornecedor e das variações nos 
fornecedores de serviços consumidos pelo cliente. 
2. Reduzir o acoplamento entre os módulos. 
Além de ser amplamente utilizado em padrões de projeto, esse padrão também é utilizado em áreas, 
como, por exemplo, balanceamento de carga e PAAS (Platform as a Service). 
Atenção 
A adição de um ou mais níveis de indireção pode causar algum impacto na performance do sistema. Embora, 
para a maioria dos sistemas de informação, o impacto não seja relevante, nos sistemas cujo tempo de resposta seja 
muito crítico, como jogos e sistemas de controle em tempo real, por exemplo, o uso excessivo de indireções poderá 
penalizar a performance. 
O padrão Variações Protegidas 
Você sabe qual é um dos maiores pesadelos de quem precisa evoluir um sistema tendo de atender 
a novas demandas em um prazo cada vez mais reduzido? É quando uma alteração aparentemente simples 
para o cliente demanda um enorme esforço de implementação, pois diversos módulos precisarão ser 
modificados para atender a esse novo requisito. Essa é uma situação claramente indesejável, ocorrendo em 
sistemas que possuem uma estrutura frágil. Mas o que causa tal fragilidade? 
Resposta 
Essa fragilidade é resultante das relações de dependência e da forma como organizamos as decisões nos 
módulos que criamos. Sempre que se projeta um sistema, é importante avaliar os aspectos que podem variar e as 
consequências resultantes dessas variações. 
Pense em um sistema da operação de bancos. Trata-se daquele que permite alguém ter uma conta 
corrente e fazer depósitos, saques, transferências, por exemplo. Essas operações existem há décadas. 
Agora reflita: nos últimos 20 anos, o que mudou nesses sistemas? 
Um aspecto que claramente vem mudando com frequência nesse período está ligado às tecnologias 
utilizadas para o cliente interagir com o sistema: caixa eletrônico, internet banking e aplicativo no celular são 
só alguns exemplos. 
Outro aspecto de mudança diz respeito às regras de execução dessas operações, como ocorre nos 
limites para saques ou nas retiradas limitadas pelo local onde você faça tal operação. Existem, enfim, muitas 
regras relacionadas à segurança das operações. 
Nesse pequeno exemplo, você já percebe que há pelo menos dois aspectos sujeitos a variações: 
A. Aspectos de interação com o usuário 
✓ A forma com que ele solicita uma transferência de valores, por exemplo, em cada interface 
onde ela esteja disponível. 
B. Regras de negócio 
✓ As políticas e regras que precisam ser observadas em cada operação desempenhada. 
Sabendo que esses elementos estão sujeitos a variações, nossa missão, como projetistas, é 
estruturar os módulos a fim de que essas variações possam ser acomodadas com um impacto localizado 
Padrões de Projetos de Software com Java 
Marcio Quirino - 123 
 
em um pequeno número de módulos, gerando, com isso, uma estrutura flexível e adaptável às mudanças 
cuja ocorrência julgamos possível. 
Vamos ilustrar essa questão com um exemplo. Você está implementando um módulo do sistema de 
vendas on-line responsável por coordenar o fechamento do pedido realizado pelo cliente. Uma das tarefas 
desse módulo é solicitar o armazenamento do pedido àquele responsávelpela persistência dos pedidos. 
Uma possível implementação para essa tarefa será ilustrada no código a seguir. 
public class ServicoFechamentoPedido { 
 private PedidoRepository; 
 
 public ServicoFechamento(PedidoRepository repository) { 
 this.pedidoRepository = repositor; 
 } 
 
 public fecharPedido(Pedido pedido) { 
 ... 
 pedidoRepository.inserirPedido(pedido); 
 ... 
 } 
} 
 
public class PedidoRepository { 
 public void inserirPedido(Pedido pedido) { 
 // monta e executa SQL INSERT com os dados do pedido 
 } 
 public void atualizarPedido(Pedido pedido) { 
 // monta e executa SQL UPDATE dos dados do pedido 
 } 
 public void excluirPedido(Pedido pedido) { 
 // monta e executa SQL DELETE do registro do pedido 
 } 
 public Pedido recuperarPedidoPorNumero(int numero) { 
 // monta e executa SQL SELECT dos dados do pedido 
 // monta objeto Pedido a partir dos dados recuperados do banco de dados 
 // retorna objeto Pedido 
 } 
} 
Classe ServicoFechamentoPedido e classe PedidoRepository. 
Vimos no código que a classe ServicoFechamentoPedido recebe um repositório (PedidoRepository) 
para o qual ele solicita a execução da operação inserirPedido. A classe PedidoRepository é uma 
implementação que faz o armazenamento e a recuperação de pedidos de um banco de dados relacional. 
Nessa solução, uma implementação (módulo cliente ServicoFechamentoPedido) está acoplada a 
outra (módulo fornecedor PedidoRepository), o que compromete a flexibilidade da solução. 
Solução das Variações Protegidas 
A solução proposta pelo padrão Variações Protegidas é identificar pontos sujeitos à variação e isolar 
essas variações com a criação de interfaces estáveis no seu entorno. 
Suponha que a nossa loja on-line tenha de funcionar com pedidos armazenados em diferentes 
servidores de bancos de dados, ou seja, nosso sistema tem de se adaptar ao servidor de banco de dados 
do lojista, que pode ser: 
A. Relacional 
✓ Como Oracle, Microsoft SQL Server e MySQL. 
B. NoSQL 
✓ Como MongoDB, Redis e Cassandra. 
Na solução original, a classe ServicoFechamentoPedido depende diretamente de uma 
implementação concreta que acessa bancos de dados relacionais. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 124 
 
Mas como poderemos projetar a solução a fim de que seja possível variar a implementação 
de armazenamento de pedidos sem que essa classe precise ser modificada? 
Quando estabelecemos uma relação de dependência, temos de avaliar se o módulo fornecedor pode 
possuir implementações alternativas. Há, por exemplo, outras formas de armazenar e recuperar pedidos 
que não seja via banco de dados relacional? 
Caso a resposta seja afirmativa, será conveniente isolar o cliente do fornecedor introduzindo uma 
abstração. O código adiante ilustra uma solução: 
public class ServicoFechamentoPedido { 
 private PedidoRepository pedidoRepository; 
 
 public ServicoFechamento(PedidoRepository repository) { 
 this.pedidoRepository = repositor; 
 } 
 
 public fecharPedido(Pedido pedido) { 
 ... 
 pedidoRepository.inserirPedido(pedido); 
 ... 
 } 
} 
 
public interface PedidoRepository { 
 void inserirPedido(Pedido pedido); 
 void atualizarPedido(Pedido pedido); 
 void excluirPedido(Pedido pedido); 
 Pedido recuperarPedidoPorNumero(int numero); 
} 
 
public class PedidoRDBMSRepository implements PedidoRepository { 
 public void inserirPedido(Pedido pedido) { 
 // monta e executa SQL INSERT com os dados do pedido 
 } 
 public void atualizarPedido(Pedido pedido) { 
 // monta e executa SQL UPDATE dos dados do pedido 
 } 
 public void excluirPedido(Pedido pedido) { 
 // monta e executa SQL DELETE do registro do pedido 
 } 
 public Pedido recuperarPedidoPorNumero(int numero) { 
 // monta e executa SQL SELECT dos dados do pedido 
 // monta objeto Pedido a partir dos dados recuperados do banco de dados 
 // retorna objeto Pedido 
 } 
} 
 
public class PedidoMongoRepository implements PedidoRepository { 
 public void inserirPedido(Pedido pedido) { 
 // monta e executa comando MongoDB para inserir os dados do pedido 
 } 
 public void atualizarPedido(Pedido pedido) { 
 // monta e executa comando MongoDB para alterar os dados do pedido 
 } 
 public void excluirPedido(Pedido pedido) { 
 // monta e executa comando MongoDB para remover os dados do pedido 
 } 
 public Pedido recuperarPedidoPorNumero(int numero) { 
 // monta e executa comando MongoDB para recuperar os dados do pedido 
 // monta objeto Pedido a partir dos dados recuperados 
 // retorna objeto Pedido 
 } 
} 
Padrões de Projetos de Software com Java 
Marcio Quirino - 125 
 
Na solução apresentada, nós protegemos o módulo cliente de variações na implementação do 
módulo fornecedor com a criação de uma interface estável, a qual, por sua vez, deve ser implementada em 
cada possível variante. 
A classe ServicoFechamentoPedido passa a utilizar a interface PedidoRepository, que é 
implementada em cada variante específica. 
Atenção 
PedidoRDBMSRepository é uma implementação das operações apresentadas em banco de dados relacional, 
enquanto PedidoMongoRepository corresponde à implementação utilizando o MongoDB. 
Consequências das Variações Protegidas 
Um dos grandes desafios do trabalho de um arquiteto ou desenvolvedor de software é identificar e 
proteger os pontos de variação de um software. O padrão Variações Protegidas constitui um dos conceitos 
mais presentes em mecanismos e padrões de desenvolvimento pela flexibilidade e proteção que ele 
proporciona em relação a variações em dados, comportamento, componentes e sistemas externos. 
Pode-se utilizar esse padrão aplicando conceitos básicos de orientação a objetos, como 
encapsulamento, interfaces e polimorfismo. Soluções mais avançadas, porém, também podem ser 
combinadas, como linguagens baseadas em regras, interpretadores de regras e metaprogramação com as 
APIs de Reflection e Annotation do Java. Vários padrões GoF, como o Adapter, Strategy, Abstract Factory 
e o Bridge, por exemplo, fazem uso do padrão Variações Protegidas. 
Atenção 
É importante verificar se o esforço envolvido para projetar e implementar uma solução que proteja partes do 
sistema de variações terá um retorno adequado. Dessa maneira, deve-se avaliar a probabilidade de as variações em 
determinados pontos do sistema acontecerem e o impacto dessas mudanças, priorizando as variações mais prováveis 
e de maior impacto. 
Considerações finais 
Neste conteúdo, verificamos como os padrões GRASP podem ser utilizados para distribuir as 
responsabilidades do sistema entre as classes com o propósito de gerar uma solução com módulos mais 
coesos e menos acoplados. Vimos ainda que o padrão Especialista na Informação estabelece uma 
heurística simples de atribuição de responsabilidade baseada no conhecimento que cada classe possui. 
Em seguida, pontuamos que o padrão Criador recomenda que os agregados sejam responsáveis por 
criar as suas partes. Observamos também que Coesão Alta e Acoplamento Baixo são padrões 
fundamentais, pois, enquanto o conceito de coesão está relacionado ao propósito específico de cada 
módulo, o padrão Acoplamento visa controlar as relações de dependência entre os módulos para que os 
impactos de futuras modificações sejam minimizados. 
Ainda apontamos que o padrão Controladoraborda a atribuição da responsabilidade de coordenar a 
produção de respostas aos eventos de sistema e que o padrão Polimorfismo é um conceito da orientação a 
objetos a permitir que as implementações dependam de abstrações, e não de outras implementações 
concretas. Por fim, destacamos que os padrões Indireção e Variações Protegidas sugerem mecanismos 
para reduzir o acoplamento entre os objetos, permitindo que os módulos fornecedores de serviços possam 
variar sem impactar os módulos clientes. 
Explore + 
Para saber mais sobre a programação orientada a objetos, acesse o site da DevMedia e leia este 
artigo: Utilização dos princípios Solid na aplicação de padrões de projeto. Consultado na internet em: 18 out. 
2021. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 126 
 
O site Patterns in Practice: Cohesion And Coupling discute e apresenta exemplos dos conceitos de 
coesão e acoplamento. 
A página Refactoring guru apresenta um conteúdo interativo e bastante completo de todos os 
padrões de projeto GoF com exemplos de código em diversas linguagens de programação. Consultado na 
internet em: 18 out. 2021. 
Referências 
GAMMA, E. et al. Design patterns: elements of reusable object-oriented software. 1. ed. Boston: 
Addison-Wesley, 1994. 
LARMAN, C. Applying UML and patterns: an introduction to object-oriented analysis and design and 
iterative development. 3. ed. Upper Saddle River: Prentice Hall, 2004. 
MARTIN, R. C. Clean architecture: a craftsman´s guide to software structure and design. 1. ed. Upper 
Saddle River: Prentice Hall, 2017. 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 127 
 
Tecnologias JPA e JEE 
Introdução 
Neste conteúdo, abordaremos a tecnologia JPA (Java Persistence API) para o mapeamento objeto-
relacional e o uso de componentes do tipo EJB (Enterprise Java Bean), elemento central do JEE (Java 
Enterprise Edition), para a implementação de regras de negócio. 
Após compreender ambas as tecnologias, analisaremos os elementos estruturais da arquitetura MVC 
(Model, View e Controller) e desenvolveremos um sistema cadastral com base nessa arquitetura, utilizando 
JPA, EJB e componentes web. Além disso, adotaremos o padrão Front Controller na camada de visualização 
para web. 
1. JPA no desenvolvimento web 
Mapeamento objeto-relacional 
Nos bancos de dados relacionais, a estrutura é baseada em tabelas que armazenam valores em 
registros, que se relacionam a partir de campos identificadores ou chaves primárias. A manutenção desses 
relacionamentos é realizada por meio de chaves estrangeiras. Por outro lado, na programação orientada a 
objetos temos as classes, cujas instâncias comportam valores, e que podem se relacionar com outras 
classes por meio de coleções ou atributos. Não existe uma estrutura de indexação, mas uma relação 
bilateral, que ocorre por meio de propriedades dos objetos envolvidos. 
Temos, então, duas filosofias distintas. Como o ambiente de programação deve gerenciar toda a 
lógica, ocorre um esforço natural para minimizar o uso de tabelas e registros, substituindo-os por classes e 
objetos. Essa abordagem fica clara quando utilizamos o padrão de desenvolvimento DAO (data access 
object), no qual temos uma classe de entidade, e as consultas e operações sobre o banco de dados são 
concentradas em uma classe gestora, com a conversão para objetos e coleções, abstraindo o enfoque 
relacional no SGBD. 
ORM (mapeamento objeto-relacional) 
O uso de DAO deu origem à técnica de mapeamento objeto-relacional, ou ORM, na qual uma 
entidade (objeto) é preenchida com os dados de um registro (relacional). Esse processo, inicialmente 
realizado de maneira programática, foi modificado com o advento de frameworks de persistência, geralmente 
baseados no uso de XML (eXtended Markup Language) ou anotações. 
 
Mapeamento objeto-relacional. 
Com base nas configurações, que indicam a relação entre atributos da classe e colunas do banco, 
bem como chaves e relacionamentos, o framework gera automaticamente todos os comandos SQL 
(Structured Query Language) necessários, transmitindo-os para o banco de dados. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 128 
 
Entity Beans 
São parte integrante do J2EE (Java 2 Enterprise Edition) e operam de acordo com o padrão Active 
Record, no qual cada operação com um objeto equivale a um comando executado no banco de dados. 
Assim, o padrão pode ser ineficiente, devido à grande quantidade de comandos SQL que poderiam ser 
executados em blocos. 
public abstract class ProdutoEntityBean implements EntityBean { 
 public abstract int getCodigo(); 
 public abstract void setCodigo(int codigo); 
 public abstract String getNome(); 
 public abstract void setNome(String nome); 
 public abstract int getQuantidade(); 
public abstract void setQuantidade(int quantidade); 
// O restante do código foi omitido 
} 
No fragmento de código, temos o início da definição de um entity bean, em que o objeto é gerado 
pelo servidor de aplicativos e as classes de entidade apresentam apenas as propriedades, além de alguns 
métodos utilitários. O mapeamento do entity bean para a tabela deve ser feito com base na sintaxe XML, 
conforme o exemplo a seguir. 
 
 
 ProdutoEntityBean 
 PRODUTO 
 
 codigo 
 COD_PRODUTO 
 
 
 nome 
 NOME 
 
 
 quantidade 
 QUANTIDADE 
 
 
 
Hibernate 
Já no framework Hibernate, o padrão DAO é implícito, com os comandos sendo gerados a partir dos 
métodos de um gestor de persistência, com base no conjunto de elementos de mapeamento e nos dados 
presentes nas entidades. 
public class Produto { 
 private int codigo; 
 private String nome; 
 private int quantidade; 
 
 public Produto(){} 
 // Os getters e setters das propriedades foram omitidos 
} 
Para o Hibernate, as entidades são apenas classes comuns, sem métodos de negócios, com um 
conjunto de propriedades e um construtor padrão. O mapeamento é realizado via XML, como no trecho 
apresentado a seguir. 
 
 
 
 
 
 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 129 
 
 
 
Com o XML, temos um modelo documental para o mapeamento, retirando do código as referências 
aos elementos do banco de dados, o que garante maior flexibilidade e menor acoplamento. 
Java Persistence API 
Além de ser verboso, o uso de XML faz com que alguns problemas surjam apenas no momento da 
execução. Um dos maiores avanços do Java foi a criação do JPA, que permitiu padronizar a arquitetura dos 
frameworks de persistência e concentrou as configurações no arquivo persistence.xml. Não é apenas uma 
biblioteca, mas uma API que define a interface comum, configurável por meio de anotações, que deve ser 
seguida pelos frameworks de persistência. 
A JPA tem o padrão DAO implícito, o que traz grande eficiência na persistência. Não é por menos 
que, na plataforma JEE atual, temos a substituição dos entity beans pelo JPA. 
Definindo uma entidade JPA 
Para definir uma entidade JPA, devemos criar uma classe sem métodos de negócios, também 
conhecida como POJO (plain old java object). A entidade definida deve receber anotações para o 
mapeamento entre a classee sua tabela, ou seja, o mapeamento objeto-relacional. 
@Entity 
@Table(name = "PRODUTO") 
@NamedQueries({ 
@NamedQuery(name = "Produto.findAll", 
query = "SELECT p FROM Produto p")}) 
public class Produto implements Serializable { 
 private static final long serialVersionUID = 1L; 
 @Id 
 @Basic(optional = false) 
 @Column(name = "COD_PRODUTO") 
 private Integer codigo; 
 @Column(name = "QUANTIDADE") 
 private Integer quantidade; 
 
 public Produto() { 
 } 
 public Produto(Integer codigo) { 
 this.codigo = codigo; 
 } 
 // Os getters e setters das propriedades foram omitidos 
 @Override 
 public int hashCode() { 
 int hash = 0; 
 hash += (codigo != null ? codigo.hashCode() : 0); 
 return hash; 
 } 
 @Override 
 public boolean equals(Object object) { 
 if (object==null||!(object instanceof Produto)) { 
 return false; 
 } 
 Produto other = (Produto) object; 
 return this.codigo!=null && 
 this.codigo.equals(other.codigo); 
 } 
 @Override 
 public String toString() { 
 return "model.Produto[ codigo=" + codigo + " ]"; 
 } 
} 
A anotação Entity define a classe Produto como uma entidade para o JPA, enquanto Table especifica 
a tabela para a qual será mapeada no banco de dados, com base no parâmetro name. Utilizamos ainda a 
Padrões de Projetos de Software com Java 
Marcio Quirino - 130 
 
anotação NamedQueries para criar consultas por meio de uma sintaxe denominada JPQL (Java Persistence 
Query Language). 
Vejamos, a seguir, as principais anotações do JPA. 
• Entity: marca a classe como uma entidade para o JPA. 
• Table: especifica a tabela que será utilizada no mapeamento. 
• Column: mapeia o atributo para o campo da tabela. 
• Id: especifica o atributo mapeado para a chave primária. 
• Basic: define a obrigatoriedade do campo ou o modo utilizado para a carga de dados. 
• OneToMany: mapeia a relação 1XN do lado da entidade principal por meio de uma coleção. 
• ManyToOne: mapeia a relação 1XN do lado da entidade dependente, com base em uma 
classe de entidade. 
• OneToOne: mapeia o relacionamento 1X1 com atributos de entidade em ambos os lados. 
• ManyToMany: mapeia o relacionamento NXN com atributos de coleção em ambos os lados. 
• OrderBy: define a regra que será adotada para ordenar a coleção. 
• JoinColumn: especifica a regra de relacionamento da chave estrangeira ao nível das tabelas. 
As entidades JPA devem conter um construtor vazio e um outro baseado na chave primária, como 
podemos verificar no código de Produto, além dos métodos equals e hashCode. Ambos os métodos 
utilitários são baseados no atributo código, que identifica a instância. 
Ainda precisamos do atributo serialVersionUID, referente à versão da classe e utilizado nos 
processos de migração da base de dados. Por fim, a implementação de toString nos dá controle sobre a 
representação da entidade como texto. 
Além das anotações nas entidades, precisamos configurar o arquivo persistence.xml, definindo os 
aspectos gerais da conexão com o banco de dados. O arquivo deve ser criado na pasta META-INF, e os 
parâmetros podem incluir elementos como a classe de conexão JDBC (Java Database Connectivity) ou o 
pool de conexões do servidor, sendo sempre presente a especificação do framework de persistência 
utilizado. 
 
 
 
 
 org.eclipse.persistence.jpa.PersistenceProvider; 
 
model.Produto 
 
 
 
 
 
 
 
 
A primeira informação relevante é o nome da unidade de persistência (ExemploSimplesJPAPU), com 
o tipo de transação que será utilizado. Transações são necessárias para garantir o nível de isolamento 
adequado entre tarefas, como no caso de múltiplos usuários acessando o mesmo banco de dados. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 131 
 
O controle transacional pode ocorrer a partir de um gestor próprio, para uso no ambiente JSE (Java 
Standard Edition) ou pelo JEE (Java Enterprise Edition) no modelo não gerenciado, mas também permite o 
modo gerenciado, por meio da integração com JTA (Java Transaction API). 
Em que temos: 
A. RESOURCE_LOCAL 
✓ Utiliza o gestor de transações do JPA, para execução no JSE ou no modelo não gerenciado 
do JEE. 
B. JTA 
✓ Ativa a integração com JTA, para utilizar o gerenciamento de transações pelo JEE. 
Em seguida, definimos o provedor de persistência no elemento provider. O elemento class define as 
classes de entidade e as propriedades da conexão são definidas no grupo properties. 
Consulta e manipulação de dados 
Com as entidades mapeadas e a conexão configurada, podemos consultar e manipular os dados 
utilizando um gestor de entidades (EntityManager). 
Entity Manager 
Concentra os métodos que invocam os comandos SQL montados pelo JPA a partir das anotações 
da entidade, de uma forma totalmente transparente. 
public class Principal { 
 public static void main(String[] args) { 
 EntityManagerFactory emf = 
 Persistence.createEntityManagerFactory( 
 "ExemploSimplesJPAPU"); 
 EntityManager em = emf.createEntityManager(); 
 Query query = em.createNamedQuery("Produto.findAll"); 
 List lista = query.getResultList(); 
 lista.forEach((e) -> { 
 System.out.println(e.getNome()); 
 }); 
 em.close(); 
 } 
} 
A. Passo 1 
✓ O primeiro passo é a definição do EntityManagerFactory, utilizando o nome da unidade de 
persistência (ExemploSimplesJPAPU). Em seguida, obtemos uma instância de 
EntityManager a partir da fábrica de gestores, utilizando o método createEntityManager. 
B. Passo 2 
✓ Com o gestor instanciado, obtemos um objeto do tipo Query, com a chamada para 
createNamedQuery, que utiliza uma NamedQuery da classe Produto. As consultas nomeadas 
devem apresentar nomes únicos pois, do contrário, poderiam gerar dualidade durante a 
execução. 
C. Passo 3 
✓ O método getResultList retorna o resultado da consulta ao SGBD em um objeto 
List. Em termos práticos, a instrução JPQL é transformada em um comando SQL, 
que é transmitido para o banco de dados via JDBC, e o resultado da consulta é convertido 
em uma coleção de objetos, a partir do mapeamento efetuado com as anotações do JPA. Ao 
final, encerramos a comunicação com o banco de dados, utilizando o método close do 
EntityManager. 
 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 132 
 
Atenção! 
Note que o JPA não elimina o uso de JDBC, pois gera apenas os comandos SQL de forma automatizada, 
utilizando anotações. 
Inclusão de dados 
Agora, podemos verificar como é feita a inclusão de um produto em nossa base de dados. 
public static void incluir(Produto p){ 
 EntityManagerFactory emf = 
 Persistence.createEntityManagerFactory( 
 "ExemploSimplesJPAPU"); 
 EntityManager em = emf.createEntityManager(); 
 try { 
 em.getTransaction().begin(); 
 em.persist(p); 
 em.getTransaction().commit(); 
 }catch(Exception e){em.getTransaction().rollback(); 
 }finally{ 
 em.close(); 
 } 
} 
Inclusões podem gerar erros, portanto, o ideal é utilizar transações para executá-las. Na verdade, 
qualquer manipulação de dados efetuada a partir do JPA exige uma transação. 
Após obtermos uma instância de EntityManager na variável em, é definido um bloco de código 
protegido, no qual a transação é iniciada com begin, seguida da inclusão do produto na base de dados por 
meio do método persist, e temos a confirmação da transação com o uso de commit. 
Caso ocorra um erro, todas as alterações efetuadas são desfeitas com o uso de rollback, e ainda 
temos um trecho finally, em que fechamos a comunicação com o uso de close, independentemente da 
ocorrência de erros. 
Para efetuar a alteração dos dados de um registro, temos um processo muito similar, trocando 
apenas o método persist por merge. 
public static void alterar(Produto p){ 
 EntityManagerFactory emf = 
 Persistence.createEntityManagerFactory( 
 "ExemploSimplesJPAPU"); 
 EntityManager em = emf.createEntityManager(); 
 try { 
 em.getTransaction().begin(); 
 em.merge(p); 
 em.getTransaction().commit(); 
 }catch(Exception e){ 
 em.getTransaction().rollback(); 
 }finally{ 
 em.close(); 
 } 
} 
Exclusão de dados 
Para excluir um registro, devemos utilizar o método find para recuperá-lo. A exclusão em si será 
executada por meio do método remove, que receberá a instância em questão. 
public static void excluir(Integer codigo){ 
 EntityManagerFactory emf = 
 Persistence.createEntityManagerFactory( 
 "ExemploSimplesJPAPU"); 
 EntityManager em = emf.createEntityManager(); 
 try { 
 em.getTransaction().begin(); 
Padrões de Projetos de Software com Java 
Marcio Quirino - 133 
 
 em.remove(em.find(Produto.class, codigo)); 
 em.getTransaction().commit(); 
 }catch(Exception e){ 
 em.getTransaction().rollback(); 
 }finally{ 
 em.close(); 
 } 
} 
Podemos concluir que os métodos find, persist, merge e remove correspondem, respectivamente, 
aos comandos SELECT, INSERT, UPDATE e DELETE, ao nível do banco de dados. 
Execução do aplicativo 
Vamos evitar aqui algumas complexidades do ambiente corporativo, direcionando nosso foco apenas 
para a interação com bancos de dados. Para isso, vamos utilizar uma aplicação Java simples. 
Veja, a seguir, como criamos a aplicação Java que iremos utilizar. 
 
"Wizard" para criação de projetos. 
Neste estudo, trabalharemos com o banco de dados Derby, também chamado de Java DB, exigindo 
a inclusão da biblioteca jdbc correspondente. 
Adicionando a biblioteca JDBC e o framework EclipseLink 
Para adicionar a biblioteca JDBC do Derby, vamos clicar com o botão direito sobre a divisão Libraries, 
e escolher a opção Add Library. Na janela seguinte, selecionaremos Java DB Driver e clicaremos no botão 
Add Library. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 134 
 
 
"Wizard" para inclusão de bibliotecas no projeto do NetBeans. 
Precisamos acrescentar o framework JPA escolhido, no caso, o EclipseLink. Veja como fazer o 
download desse framework na seção Explore +. 
Após efetuar o download da versão mais recente do EclipseLink no formato zip e extrair para algum 
diretório de fácil acesso, crie uma biblioteca utilizando a opção de menu Tools.Libraries. Em seguida, clique 
em New Library. A essa nova biblioteca daremos o nome EclipseLink2.7, e adicionaremos o arquivo 
eclipselink.jar, presente no diretório jlib, além de todos os arquivos no formato jar do subdiretório jpa. Veja 
na imagem a seguir. 
 
"Wizard" para inclusão de bibliotecas no projeto do NetBeans. 
Após a definição da biblioteca, vamos adicionar ao projeto, da mesma forma que fizemos para o 
driver JDBC. Ao final, teremos a configuração de bibliotecas para o projeto conforme a imagem a seguir. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 135 
 
 
Navegador do projeto no NetBeans. 
Criando o banco de dados Derby 
Agora, só precisamos de um banco de dados Derby, que será criado de forma simples, por meio da 
aba Services do NetBeans, na divisão Databases. Veja na imagem a seguir. 
 
Navegador de serviços no NetBeans. 
Para criarmos um banco de dados, clique com o botão direito sobre o driver Java DB da árvore de 
Databases e escolha da opção Create Database. Na janela seguinte, preencha o nome do banco de dados, 
o usuário e a senha com o valor bancoJPA. Veja na imagem a seguir. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 136 
 
 
"Wizard" para criação de banco de dados derby no NetBeans. 
A conexão é aberta com um duplo-clique sobre o identificador do banco de dados. Após conectado, 
execute os comandos SQL de criação da tabela, clicando com o botão direito sobre a conexão e escolhendo 
a opção Execute Command. 
Veremos que a janela de edição de SQL será aberta, permitindo que seja digitado o script 
apresentado a seguir. Para executar nosso script, devemos pressionar CTRL+SHIFT+E, ou clicar no botão 
de execução de SQL na parte superior do editor. 
CREATE TABLE PRODUTO ( 
 COD_PRODUTO INTEGER NOT NULL PRIMARY KEY, 
 NOME VARCHAR(50), 
 QUANTIDADE INTEGER); 
 
INSERT INTO PRODUTO VALUES (1,'Morango',200); 
INSERT INTO PRODUTO VALUES (2,'Banana',1000); 
INSERT INTO PRODUTO VALUES (3,'Manga',600); 
 
SELECT * FROM PRODUTO; 
Resultado da execução do aplicativo 
Ao rodar o programa, a listagem da tabela com os registros inseridos será apresentada na própria 
janela de edição, em uma divisão própria. Agora, podemos executar nosso projeto, gerando a saída 
apresentada a seguir. 
 
Saída do programa apresentada no console. 
Manipulando dados com NamedQueries 
Como já vimos, é extremamente necessário estabelecer a comunicação entre a aplicação e o banco 
de dados. Normalmente, precisamos realizar alguma consulta parametrizada, o que inclui receber os 
parâmetros, como uma chave primária para identificação, e enviar a instrução para o banco de dados. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 137 
 
Roteiro de prática 
Avançaremos um pouco mais, propondo a você o seguinte desafio: alterar o programa apresentado 
anteriormente, de forma que a classe principal.Principal utilize o código para selecionar um produto 
específico. Para isto, é importante realizar as etapas a seguir. 
• Utilizar uma anotação para criar uma NamedQuery na classe Produto 
• Alterar a NamedQuery utilizada em Principal 
• Incluir os parâmetros no objeto query 
Veja o resultado dessa prática. 
Classe “modelo.Produto”: 
@Entity 
@Table(name = "PRODUTO") 
@NamedQueries({ 
 @NamedQuery(name = "Produto.findAll", query = "SELECT p FROM Produto p"), 
 @NamedQuery(name = "Produto.findByCodProduto", query = "SELECT p FROM Produto p WHERE 
p.codProduto = :codProduto") 
}) 
public class Produto implements Serializable { 
// código omitido 
 
Classe "principal.Principal": 
public class Principal { 
 
 public static void main(String[] args) { 
 EntityManagerFactory emf 
 = Persistence.createEntityManagerFactory( 
 "ExemploSimplesJPAPU"); 
 EntityManager em = emf.createEntityManager(); 
 Query query = em.createNamedQuery("Produto.findByCodProduto"); 
 query.setParameter("codProduto", 1); 
 List lista = query.getResultList(); 
 lista.forEach((e) -> { 
 System.out.println(e.getCodProduto() + "-" + e.getNome()); 
 }); 
 em.close(); 
 } 
} 
2. Implementação de regras de negócio com EJBs 
Enterprise Java Beans (EJB) 
No ambiente Java, a arquitetura de objetos distribuídos geralmente é o elemento central dos 
servidores corporativos. Podemos observar a importância dessa arquitetura no uso de componentes EJB 
(Enterprise Java Bean)em servidores de aplicação, como o Glassfish. 
Os EJB são componentes corporativos utilizados de forma indireta dentro de um ambiente de objetos 
distribuídos, suportando elementos da plataforma JEE (Java Enterprise Edition). Todo EJB é executado em 
um pool de objetos, cujo número de instâncias varia de acordo com a demanda, segundo um intervalo de 
tempo estabelecido. 
Podemos acessar os serviços oferecidos pelo pool de EJBs por meio de interface local 
(EJBLocalObject) ou remota (EJBObject), gerada a partir de componentes de fábrica, criados com a 
implementação de EJBLocalHome, para acesso local, ou EJBHome, para acesso remoto. Como é padrão 
na plataforma Java, as fábricas são registradas e localizadas via JNDI (Java Naming and Directory Interface). 
O processo para acessar o pool de EJBs envolve três passos: 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 138 
 
A. Passo 1 
✓ Acesso à fábrica de interfaces, por meio de JNDI. 
B. Passo 2 
✓ Geração da interface de acesso pela fábrica. 
C. Passo 3 
✓ Entrega da interface ao cliente, permitindo iniciar o diálogo com o pool. 
Veja a representação desses passos na imagem a seguir. 
 
Servidor e contêineres Java EE. 
O acesso ao banco de dados é realizado por meio de um pool de conexões JDBC, representado por 
um objeto do tipo DataSource, que é registrado via JNDI. Quando solicitamos uma conexão ao DataSource, 
não estamos abrindo uma nova conexão, mas reservando uma das disponíveis no pool. Quando invocamos 
o método close, não ocorre a desconexão, mas sim a liberação da conexão para a próxima requisição. 
No código a seguir, temos um exemplo de utilização de pool de conexões. Após obter o recurso via 
JNDI, por meio do método lookup de InitialContext, efetuamos a conversão para o tipo correto, no caso, um 
DataSource que fornece conexões através do método getConnection. O restante da programação é a 
mesma de um acesso local ao banco de dados. 
protected void processRequest(HttpServletRequest request, HttpServletResponse response) 
 throws ServletException, IOException { 
 response.setContentType("text/html;charset=UTF-8"); 
 try (PrintWriter out = response.getWriter()) { 
 out.println(""); 
 try { 
 InitialContext ctx = new InitialContext(); 
 DataSource dts = (DataSource) ctx.lookup("jdbc/loja"); 
Padrões de Projetos de Software com Java 
Marcio Quirino - 139 
 
 Connection c1 = dts.getConnection(); 
 Statement st = c1.createStatement(); 
 ResultSet rs = st.executeQuery("SELECT * FROM PRODUTO"); 
 while(rs.next()) 
 out.println(rs.getString("NOME")+" 
 "); 
 c1.close(); 
 } catch (SQLException | NamingException ex) { 
 } 
 out.println(""); 
 } 
} 
O JPA abstrai a localização e utilização do pool, que fica a cargo do framework de persistência. No 
fluxo normal de execução, o cliente faz uma solicitação para a interface de acesso, que é repassada para o 
pool. O pool disponibiliza um EJB para responder à solicitação. Já na programação do EJB, utilizamos o 
JPA para obter acesso ao banco de dados. 
A programação, no modelo adotado a partir do JEE5, é bastante simples, e precisaremos apenas 
das anotações corretas para que os application servers, como o JBoss ou o GlassFish, se encarreguem de 
montar toda a estrutura necessária. Isso é bem diferente do processo de criação adotado pelo J2EE, 
envolvendo uma grande quantidade de interfaces, classes e arquivos XML, com a verificação sendo 
realizada apenas no momento da implantação do sistema. 
Session beans 
Esses componentes fundamentais no desenvolvimento de aplicações empresariais em Java são 
responsáveis por encapsular a lógica de negócios e fornecer serviços específicos para os clientes. Os 
session beans são amplamente utilizados para implementar transações, acesso a bancos de dados e outras 
operações relacionadas à lógica de negócios. 
Os EJBs são utilizados para implementar as regras de negócio do nosso aplicativo com base nas 
entidades e nos requisitos definidos. Esses componentes não podem acumular a responsabilidade sobre a 
estrutura de persistência utilizada. Também devemos observar que as regras de negócio devem ser 
totalmente independentes das interfaces do sistema. 
O primeiro tipo de EJB que deve ser observado é o de sessão (session beans), responsável por 
efetuar processos de negócios de forma síncrona, e configurável, podendo apresentar três comportamentos 
distintos: 
A. Stateless 
✓ Não permite a manutenção de estado, ou seja, não guarda valores entre chamadas 
sucessivas. 
B. Stateful 
✓ É utilizado quando é necessário manter valores entre chamadas sucessivas, como no caso 
de somatórios. 
C. Singleton 
✓ Permite apenas uma instância por máquina virtual, garantindo o compartilhamento de dados 
entre todos os usuários. 
Stateless e stateful 
Utilizamos stateless quando não precisamos de informações dos processos anteriores ao corrente. 
Qualquer instância do pool de EJBs pode ser escolhida, e não é necessário efetuar a carga de dados 
anteriores, definindo o padrão de comportamento mais ágil para um session bean. 
O comportamento stateful deve ser utilizado apenas quando precisamos de informações anteriores, 
como em uma cesta de compras virtual, ou processos com acumuladores em cálculos estatísticos, entre 
outras situações com necessidade de gerência de estados. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 140 
 
Interface de acesso 
Antes de definir um session bean, devemos definir sua interface de acesso, com base na anotação 
Local, para acesso interno, ao nível do servidor, ou Remote, permitindo que o componente seja acessado 
remotamente. Em nossos estudos, será suficiente o uso de acesso local, já que teremos o acionamento dos 
EJBs a partir dos servlets. 
@Local 
public interface CalculadoraLocal { 
 int somar(int a, int b); 
} 
Ao criarmos o EJB, ele deverá implementar a interface de acesso, além de ser anotado como 
stateless ou stateful, dependendo da necessidade do negócio. Para uma calculadora simples, não 
precisaríamos de gerência de estados. 
@Stateless 
public class Calculadora implements CalculadoraLocal { 
 @Override 
 public int somar(int a, int b) { 
 return a + b; 
 } 
} 
Singleton 
O session bean que implementa o Singleton é utilizado para compartilhar dados entre os usuários 
conectados, mesmo na execução em ambientes distribuídos. É importante observar que a tecnologia de 
EJBs é empregada em sistemas de missão crítica, que costumam trabalhar com clusters de computadores. 
Session bean com Servlet 
O código a seguir representa o processo de utilização de um session bean a partir de um Servlet. 
@WebServlet(name = "ServletSoma", 
urlPatterns = {"/ServletSoma"}) 
public class ServletSoma extends HttpServlet { 
 
 @EJB 
 CalculadoraLocal facade; 
 
 protected void doGet(HttpServletRequest request, 
 HttpServletResponse response) 
 throws ServletException, IOException { 
 response.setContentType("text/html;charset=UTF-8"); 
 try (PrintWriter out = response.getWriter()) { 
 out.println(""); 
 
 out.println("Servlet ServletSoma: " + 
 facade.somar(2, 3) + ""); 
 out.println(""); 
 out.println(""); 
 } 
 } 
} 
Tudo que precisamos fazer é anotar um atributo, do tipo da interface local, com EJB. No exemplo, 
temos a interface CalculadorLocal referenciada no atributo facade, o que permite invocar o método somar, 
sendo executado pelo pool. 
Quase todos os processos de negócio de um sistema corporativo são implementados pormeio de 
session beans. Contudo, alguns comportamentos não podem ser definidos de forma síncrona, exigindo 
comunicação com mensagerias, seguindo um modelo assíncrono. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 141 
 
Message-driven beans (MDB) 
Protocolos de mensageria desempenham um papel fundamental em sistemas Java. Esses 
protocolos definem conjuntos de regras e formatos utilizados para facilitar a troca de mensagens entre 
diferentes sistemas. Eles determinam como as mensagens são enviadas, recebidas, processadas e 
entregues aos MDBs (message-driven Beans). Esses protocolos oferecem recursos como persistência de 
mensagens, filas de mensagens e garantia de entrega, possibilitando uma comunicação confiável e 
assíncrona para os MDBs. 
Mensagerias 
As mensagerias atuam de forma assíncrona e podem ser: 
 
Modelo publish/subscribe 
As mensagens são depositadas em tópicos para que os assinantes as recuperem. 
 
Modelo point to point 
As mensagens são enfileiradas para um tratamento sequencial no destinatário. 
 
O uso de mensagerias permite a construção de sistemas B2B quase sem acoplamento, em que o 
único elemento de ligação entre os sistemas das duas empresas é a mensagem transmitida entre elas. 
Após o emissor publicar uma mensagem, a gerência passa a ser da mensageria, até a retirada pelos 
receptores. Mesmo que o receptor esteja inativo, as mensagens não se perdem, sendo acumuladas até o 
momento em que o receptor seja ativado. 
Para criar filas ou tópicos no GlassFish, é necessário utilizar o comando asadmin, como no exemplo 
seguinte, para a criação de uma fila denominada jms/SimpleQueue. 
asadmin create-jms-resource –restype javax.jms.ConnectionFactory 
jms/SimpleConnectionFactory 
 
asadmin create-jms-resource --restype javas.jms.Queue jms/SimpleQueue 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 142 
 
Também podemos abrir o console do asadmin sem a passagem de parâmetros e invocar os 
comandos internamente. Vejamos. 
 
Comandos executados no shell do asadmin. 
MDB (message-driven bean) 
Existe um tipo de EJB denominado MDB (message-driven bean), que tem como finalidade a 
comunicação com mensagerias via JMS (Java Message Service), possibilitando o processamento 
assíncrono no JEE. Com o MDB, é possível trabalhar nos dois domínios de mensagerias, com o tratamento 
via pool de EJBs, a partir do método onMessage, representando os eventos de recepção de mensagens. 
@MessageDriven(activationConfig = { 
@ActivationConfigProperty(propertyName = "destinationLookup", 
propertyValue = "jms/SimpleQueue"), 
@ActivationConfigProperty(propertyName = "destinationType", 
propertyValue = "javax.jms.Queue") 
}) 
public class Mensageiro001 implements MessageListener { 
 
 public Mensageiro001() { 
 } 
 
 @Override 
 public void onMessage(Message message) { 
 try { 
 System.out.println("Mensagem enviada: "+ 
 ((TextMessage) message).getText()); 
 } catch (JMSException ex) { 
 System.out.println("Erro: "+ex.getMessage()); 
 } 
 } 
} 
No código de exemplo, o MDB é definido utilizando a anotação MessageDriven, com a configuração 
para acesso a jms/SimpleQueue, do tipo javax.jms.Queue, por meio de anotações ActivationConfigProperty. 
Também é possível utilizar canais internos do projeto, mas o uso de canais do servidor viabiliza o 
comportamento B2B, com acesso a partir de qualquer plataforma que dê suporte ao uso de mensagerias. 
Para o tratamento das mensagens, devemos implementar a interface MessageListener, que contém 
apenas o método onMessage. A mensagem é recebida no parâmetro do tipo Message, sendo necessário 
converter para o tipo correto, como no exemplo, em que temos a captura de um texto enviado via 
TextMessage, e a impressão da mensagem no console do GlassFish. 
Saiba mais 
O MDB foi projetado exclusivamente para receber mensagens a partir de filas ou tópicos, o que faz com que 
não possa ser acionado diretamente, como os session beans. Para sua ativação, basta que um cliente poste uma 
mensagem. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 143 
 
O código é feito da seguinte forma: 
@WebServlet(name = "ServletMessage", 
urlPatterns = {"/ServletMessage"}) 
public class ServletMessage extends HttpServlet { 
 @Resource(mappedName = "jms/SimpleConnectionFactory") 
 private ConnectionFactory connectionFactory; 
 @Resource(mappedName = "jms/SimpleQueue") 
 private Queue queue; 
 
 public void putMessage() throws ServletException { 
 try { 
 Connection connection = 
 connectionFactory.createConnection(); 
 Session session = 
 connection.createSession(false, 
 Session.AUTO_ACKNOWLEDGE); 
 MessageProducer messageProducer = 
 session.createProducer(queue); 
 TextMessage message = session.createTextMessage(); 
 message.setText("Teste com MDB"); 
 messageProducer.send(message); 
 } catch (JMSException ex) { 
 throw new ServletException(ex); 
 } 
 } 
 
 protected void doGet(HttpServletRequest request, 
 HttpServletResponse response) 
 throws ServletException, IOException { 
 response.setContentType("text/html;charset=UTF-8"); 
 try (PrintWriter out = response.getWriter()) { 
 out.println(""); 
 out.println(""); 
 
 putMessage(); 
 
 out.println("Mensagem Enviada"); 
 out.println(""); 
 } 
 } 
} 
O processo é um pouco mais complexo que o adotado para os session beans, mas apresenta menor 
acoplamento. Veja o passo a passo: 
A. Passo 1 
✓ Mapear a fábrica de conexões da mensageria e a fila de destino do MDB, por meio de 
anotações Resource. 
B. Passo 2 
✓ Definir o método putMessage para envio da mensagem, criando uma conexão a partir da 
fábrica, uma sessão a partir da conexão, e o produtor de mensagens (MessageProducer) a 
partir da sessão. 
C. Passo 3 
✓ Criar a mensagem de texto (TextMessage) por meio da sessão, definir o texto que será 
enviado com setText, e finalmente enviar a mensagem, finalizando a definição do método 
putMessage. 
Veja a representação na imagem a seguir: 
Padrões de Projetos de Software com Java 
Marcio Quirino - 144 
 
 
Console do Glassfish exibindo as mensagens de teste. 
Aplicativo corporativo 
Aplicativos Java corporativos são softwares desenvolvidos para atender às necessidades complexas 
e específicas das organizações. Eles são construídos utilizando tecnologias Java Enterprise Edition (Java 
EE), fornecendo recursos avançados, como gerenciamento de transações, segurança, escalabilidade e 
integração com sistemas legados. Esses aplicativos são projetados para lidar com grandes volumes de 
dados e suportar operações críticas para o funcionamento das empresas. 
EJBs 
Para que possamos trabalhar com EJBs, por meio do ambiente do NetBeans, devemos definir um 
projeto corporativo. A sequência de passos para criar o aplicativo corporativo pode ser observada a seguir. 
A. Passo 1 
✓ Criar um projeto do tipo Enterprise Application, na categoria Java Enterprise. 
 
B. Passo 2 
✓ Preencher o nome (ExamploEJB) e local do projeto. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 145 
 
 
C. Passo 3 
✓ Escolher o servidor (GlassFish) e versão do JEE (Java EE7), além de marcar as opções de 
criação para os módulos EJB e web. 
 
Seguindo corretamente os passos apresentados, você vara que serão gerados três projetos. 
Poderemos visualizá-los na interface do NetBeans. 
 
A seguir, apresentamos as características de cada um dos projetos. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 146 
 
1. ExemploEJB-ejb 
✓ Utilizado na definiçãodas entidades JPA e dos componentes EJB, sendo compilado com a 
extensão "jar". 
2. ExemploEJB-war 
✓ Contém os elementos para web, como servlets, facelets e páginas XHTML, compilados para 
um arquivo "war". 
3. ExemploEJB 
✓ Agrupa os dois projetos anteriores, compactados em apenas um arquivo, com adoção da 
extensão "ear", para implantação. 
Quando trabalhamos com um projeto corporativo, devemos implantar o projeto principal, com 
extensão ear (enterprise archived), cujo ícone é um triângulo, pois qualquer tentativa de implantar os dois 
projetos secundários irá impedir a execução correta do conjunto, exigindo que seja feita a remoção manual 
dos projetos anteriores pela aba de serviços. 
 
Remoção de aplicação web do servidor Glassfish. 
Agora, vamos criar nosso primeiro session bean, configurado como stateless, no projeto secundário 
ExemploEJB-ejb, adicionando um novo arquivo e seguindo alguns passos: 
A. Passo 1 
✓ Selecionar o tipo de session bean na categoria Enterprise Java Beans. 
 
B. Passo 2 
✓ Definir o nome (Calculadora) e pacote (ejbs) do novo session bean, escolher o tipo como 
Stateless e marcar apenas a interface Local 
Padrões de Projetos de Software com Java 
Marcio Quirino - 147 
 
 
Em diversas situações, a IDE mostra um erro de compilação decorrente da importação dos 
componentes da biblioteca javax.ejb. Caso esse problema ocorra, a solução é simples com a inclusão da 
biblioteca Java EE 7 API no projeto. 
 
Após incluir as bibliotecas necessárias, podemos completar os códigos de Calculadora e 
CalculadoraLocal, de acordo com os exemplos apresentados anteriormente, e iremos testar o EJB, ainda 
seguindo os exemplos, por meio de um servlet. Como os componentes web são criados ao nível de 
ExemploEJB-war, devemos acessar o projeto e adicionar um novo arquivo do tipo servlet, na categoria web, 
com o nome ServletSoma e pacote servlets, sem adicionar informações ao arquivo web.xml. 
Com o componente ServletSoma criado, utilizamos o código de exemplo definido antes, com a 
chamada para o EJB, executamos o projeto principal (ExemploEJB), e efetuamos a chamada apropriada: 
http://localhost:8080/ExemploSimplesJPA-war/ServletSoma. 
 
http://localhost:8080/ExemploSimplesJPA-war/ServletSoma
Padrões de Projetos de Software com Java 
Marcio Quirino - 148 
 
MDBs 
Para criar o EJB do tipo MDB, devemos adicionar, no projeto ExemploEJB-ejb, um novo arquivo do 
tipo message-driven bean, na categoria Enterprise Java Beans. 
Precisamos definir o nome (Mensageiro001) e o pacote do componente, além de escolher a fila para 
as mensagens no servidor (jms/SimpleQueue), como na imagem a seguir. 
 
Todos os passos para a codificação de Mensageiro001 foram descritos anteriormente, bem como a 
do servlet para postagem da mensagem, com o nome ServletMessage, mas lembre-se de que o servlet deve 
ser criado no projeto ExemploEJB-war. Com todos os componentes implementados, basta implantar o 
sistema, a partir do projeto principal, e efetuar a chamada correta no navegador pelo endereço: 
"http://localhost:8080/ExemploSimplesJPA-war/ServletMessage". 
Após efetuar a chamada, você verá a página com a informação de que ocorreu o envio da mensagem, 
o que poderá ser verificado no console de saída do GlassFish, conforme descrito anteriormente. 
Aplicando os MDBs 
No mundo web é bastante comum a utilização de recursos externos, sejam eles de outro projeto da 
sua organização ou mesmo de outra organização. Esses recursos podem ser desenvolvidos utilizando 
linguagens e respeitando diferentes padrões. Para facilitar a comunicação, uma excelente alternativa é a 
troca de mensagens. 
Roteiro de prática 
No mundo Java, utilizamos muito os MDB (message-driven beans). Portanto, vamos exercitar a 
criação desse tipo de componente desenvolvendo duas classes no mesmo projeto: uma para receber 
mensagens e outra para produzir. Para isso, vamos usar o padrão point-to-point, conforme as etapas a 
seguir: 
• Crie a fábrica de conexões e a fila no glassfish, utilizando o asadmin. 
• No projeto ejb, desenvolva a classe MeuReceptor, responsável por tratar as mensagens. 
• No projeto war, desenvolva uma classe MeuProdutor, que contenha um servlet para enviar 
uma mensagem para o receptor. 
• No projeto war, altere o index.html com um formulário de envio de mensagem. 
• Verifique o resultado no console do glassfish. 
Veja o resultado dessa prática. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 149 
 
"MeuReceptor.java" 
@MessageDriven(activationConfig = { 
@ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = 
"jms/MinhaQueue"), 
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = 
"javax.jms.Queue") 
}) 
 
public class MeuReceptor implements MessageListener { 
 
 public MeuReceptor() { 
 } 
 
 @Override 
 public void onMessage(Message message) { 
 try { System.out.println("Mensagem enviada: " 
 + ((TextMessage) message).getText()); 
 } catch (JMSException ex) { 
 System.out.println("Erro: " + ex.getMessage()); 
 } 
 } 
} 
"MeuProdutor.java" 
@WebServlet("/MeuProdutor") 
public class MeuProdutor extends HttpServlet { 
 
 @Resource(mappedName = "jms/MinhaConnectionFactory") 
 private ConnectionFactory connectionFactory; 
 @Resource(mappedName = "jms/MinhaQueue") 
 private Queue queue; 
 
 public void putMessage(String mensagem) { 
 try { 
 Connection connection = connectionFactory.createConnection(); 
 Session session = connection.createSession(false, 
 Session.AUTO_ACKNOWLEDGE); 
 MessageProducer messageProducer 
 = session.createProducer(queue); 
 TextMessage message = session.createTextMessage(); 
 message.setText(mensagem); 
 messageProducer.send(message); 
 } catch (JMSException ex) { 
 
 } 
 } 
 @Override 
 protected void doGet(HttpServletRequest request, 
 HttpServletResponse response) throws IOException{ 
 response.setContentType("text/html;charset=UTF-8"); 
 request.setCharacterEncoding("UTF-8"); 
 String mensagem = request.getParameter("mensagem"); 
 putMessage(mensagem); 
 PrintWriter out = response.getWriter(); 
 out.println("Sua Mensagem foi Enviada"); 
 out.println("Mensagem: " + mensagem); 
 } 
} 
"Index.html" 
 
 
TODO supply a title 
 
 
 
 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 150 
 
 
 
 
 
 
3. Arquitetura MVC no Java 
Padrões de desenvolvimento 
A orientação a objetos representou um grande avanço na implementação de sistemas, pois 
aproximou a modelagem da codificação. Nesse contexto, os padrões de desenvolvimento fornecem ainda 
mais vantagens ao desenvolvimento, definindo soluções reutilizáveis, com nome, descrição da finalidade, 
modelagem UML e modo de utilização. 
Entre os diversos padrões de projeto, alguns se destacam nos sistemas corporativos, como Facade, 
Proxy, Flyweight, Front Controller e DAO (data access object). Na tecnologia JPA observamos o padrão 
DAO, com comandos para acesso ao banco de dados agrupados em classes específicas, separadas do 
restante do sistema. 
Descrição dos padrões de desenvolvimento 
Vejamos uma descrição formal dos padrões de desenvolvimento citados, além de alguns outros, 
comuns em sistemas criados para o ambiente JEE. 
• Abstract Factory: definição de uma arquitetura abstrata para a geração de objetos, muito 
comum em frameworks. 
• Command: encapsula o processamentoda resposta para algum tipo de requisição, muito 
utilizado para o tratamento de solicitações feitas no protocolo HTTP. 
• Data Access Object: utiliza classes específicas para concentrar as chamadas para o banco 
de dados. 
• Facade: encapsula as chamadas para um sistema complexo, muito utilizado em ambientes 
corporativos. 
• Flyweight: cria grupos de objetos que respondem a uma grande quantidade de chamadas. 
• Front Controller: concentra as chamadas para o sistema, efetuando os direcionamentos 
corretos para cada chamada. 
• Iterator: permite acesso sequencial aos objetos de uma coleção, o que é implementado 
nativamente no Java. 
• Proxy: Define um objeto para substituir a referência de outro, utilizado nos objetos remotos 
para deixar a conexão transparente para o programador. 
• Service Locator: gerencia a localização de recursos compartilhados, com base em serviços 
de nomes e diretórios. 
• Singleton: garante a existência de apenas uma instância para a classe, como em controles 
de acesso. 
• Strategy: seleciona algoritmos em tempo de execução, com base em algum parâmetro 
fornecido. 
Atualmente, os sistemas combinam diversos padrões de projeto em suas implementações. 
Exemplo 
Criação de um pool de processadores de resposta para solicitações de usuários remotos, o que poderia ser 
caracterizado por um Flyweight de Strategies para a escolha de Commands, além da utilização de Proxy na 
comunicação com os clientes. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 151 
 
Padrões arquiteturais 
A arquitetura de um sistema define sua estrutura de alto nível, formalizando a organização em termos 
de componentes e interações. Um dos objetivos é aproximar a visão de projeto da implementação do 
sistema, impedindo que ocorra a previsão de funcionalidades inviáveis para a fase de codificação. 
As especificações da arquitetura também definem as interfaces de entrada ou saída de informações, 
forma de comunicação com outros sistemas, e regras para o agrupamento dos componentes com base em 
suas áreas de atuação, entre outras características. Entenda: 
A. Modelo arquitetural 
✓ Define a arquitetura de forma abstrata, com foco apenas no objetivo ou característica 
principal. 
B. Padrão arquitetural 
✓ Define o perfil dos componentes estruturais, o modelo de comunicação e até os padrões de 
desenvolvimento mais adequados. 
Por exemplo, o modelo de objetos distribuídos define apenas atributos essenciais para delimitar um 
ambiente com coleções de objetos respondendo a requisições. 
Existem diferentes definições de modelos para os padrões arquiteturais, e alguns deles satisfazem a 
mais de um modelo. Vejamos. 
Padrão arquitetural Modelo(s) 
Broker Sistemas Distribuídos 
Camadas Mud to structure, chamada e retorno 
Orientado a objetos Chamada e retorno 
Programa principal e sub-rotina Chamada e retorno 
Pipes/Filters Mud to structure, fluxo de dados 
Blackboard Mud to structure, centrada em dados 
Lote Fluxo de dados 
Repositório Centrada em dados 
Processos comunicantes Componentes independentes 
Event-Driven Componentes independentes 
Interpretador Máquina virtual 
Baseado em regras Máquina virtual 
MVC Sistemas interativos 
PAC Sistemas interativos 
Microkernel Sistemas adaptáveis 
Reflexiva Sistemas adaptáveis 
 
A arquitetura de referência mapeia um padrão arquitetural para componentes de software, que são 
capazes de implementar as funcionalidades requeridas de forma cooperativa. Definida a referência, 
finalmente pode ser construída uma arquitetura, com todos os componentes adequadamente codificados. 
Um exemplo de arquitetura é o VisiBroker, da fabricante Inprise, que utiliza como referência o CORBA 
(common object request broker architecture), baseado no modelo de objetos distribuídos, segundo o padrão 
arquitetural Broker. Veja essa representação na imagem a seguir. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 152 
 
 
Para utilizar uma arquitetura, é necessário compreender sua finalidade, de acordo com o padrão 
arquitetural adotado. Veja: 
A. Sistemas de baixa complexidade 
✓ Podem basear a arquitetura nos paradigmas adotados para a codificação, como a orientação 
a objetos. 
B. Sistemas de alta complexidade 
✓ Exigem uma padronização mais robusta, incluindo a forma de comunicação em rede, gerência 
de pools de objetos, entre outros aspectos. 
Ambientes de execução remota, como RPC (remote procedure call) e Web Services, são baseados 
em arquiteturas no padrão de processos comunicantes. Nesses ambientes, servidores e clientes podem ser 
criados utilizando plataformas de desenvolvimento distintas, e o único fator de acoplamento é o protocolo 
de comunicação adotado. 
É comum o uso de mensagerias nos sistemas corporativos, nas quais utilizamos o padrão arquitetural 
event-driven, baseado na ativação de processos de forma indireta, a partir de mensagens. O papel das 
mensagerias é tão importante que o componente adotado na comunicação é chamado de MOM (message-
oriented middleware). As vantagens no uso desse padrão são o acoplamento quase nulo e o processamento 
assíncrono. 
Um sistema simples pode responder a um único padrão arquitetural, mas os sistemas corporativos 
são complexos e heterogêneos, sendo muito comum a adoção de múltiplos padrões combinados. 
Normalmente, é adotado um padrão principal, que, no caso dos sistemas cadastrais, é o MVC. 
Arquitetura MVC 
A arquitetura MVC (model-view-controller) divide o sistema em três camadas, com responsabilidades 
específicas. 
1. Model: nessa camada, temos as entidades e as classes para acesso ao banco de dados. 
2. Controller: aqui, concentramos os objetos de negócio. 
3. View: nessa camada, são definidas as interfaces do sistema com o usuário ou com outros 
sistemas. 
Camadas da MVC 
A seguir, vamos ver as principais características de cada camada que compõe a arquitetura MVC. 
A. Model (modelo) 
✓ Controla toda a persistência do sistema. 
✓ Concentra as chamadas ao banco de dados. 
✓ Encapsula o estado do sistema. 
✓ Pode utilizar mapeamento objeto-relacional. 
Padrão DAO é aplicável. 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 153 
 
B. Controller (controlador) 
✓ Implementa as regras de negócio do sistema. 
✓ Solicita os dados à camada Model. 
✓ Não pode ser direcionada para uma interface. 
✓ Pode utilizar objetos distribuídos. 
✓ Padrão Facade facilita a utilização da camada. 
C. View (visualização) 
✓ Define a interface do sistema. 
✓ Faz requisições para a camada Controller. 
✓ Contém apenas regras de formatação. 
✓ Podem ser definidas múltiplas interfaces. 
✓ Não pode acessar a camada Model. 
Uma regra fundamental para a arquitetura MVC é a de que os elementos da camada View não podem 
acessar a camada Model. Somente os objetos de negócio da camada Controller podem acessar os 
componentes da model, e os elementos da View devem fazer suas requisições exclusivamente para os 
objetos de negócio. 
A arquitetura MVC é baseada em camadas, e cada camada enxerga apenas a camada 
imediatamente abaixo. 
Em uma arquitetura MVC, as entidades são as unidades de informação para o trânsito entre as 
camadas. Todos os comandos SQL ficam concentrados nas classes DAO. Como apenas a camada 
Controller pode acessar a Model, e nela estão as classes DAO, garantimos que as interfaces não acessem 
o banco de dados diretamente. 
Como as instruções SQL são bastante padronizadas, é possível criar ferramentas para a geração 
dos comandos e preenchimento das entidades, bastando expressar a relação entre atributos da entidade e 
campos do registro. 
A camada Controller precisa ser definida de maneira independente de ambiente específico, como 
interfaces SWING ou protocolo HTTP. A única dependência aceitável para os objetos de negócio deve ser 
com relação à camada Model. Como são os modelos que gerenciam os componentes DAO, isso diminui a 
complexidade nas atividades cadastrais iniciadas148 
Aplicando os MDBs ................................................................................................................................ 148 
Roteiro de prática ............................................................................................................................... 148 
3. Arquitetura MVC no Java ..................................................................................................... 150 
Padrões de desenvolvimento ................................................................................................................. 150 
Descrição dos padrões de desenvolvimento ...................................................................................... 150 
Padrões arquiteturais ............................................................................................................................. 151 
Arquitetura MVC ..................................................................................................................................... 152 
Camadas da MVC .............................................................................................................................. 152 
Componentes Java para MVC ............................................................................................................... 153 
Implementação de MVC no ambiente Java ............................................................................................ 156 
Roteiro de prática ............................................................................................................................... 156 
4. Padrão Front Controller em sistemas MVC ......................................................................... 158 
Padrão Front Controller .......................................................................................................................... 158 
Camadas Model e Controller .................................................................................................................. 160 
Criação da camada Model.................................................................................................................. 160 
Criação da camada Controller ............................................................................................................ 163 
Padrões de Projetos de Software com Java 
Marcio Quirino - 7 
 
Construção dos session beans .......................................................................................................... 165 
Camada View ......................................................................................................................................... 168 
Implementação do Front Controller ........................................................................................................ 170 
Aplicação MVC com padrão Front Controller ......................................................................................... 174 
Roteiro de prática ............................................................................................................................... 174 
Explore + ................................................................................................................................................ 176 
Referências ............................................................................................................................................ 176 
Padrões de Projetos de Software com Java 
Marcio Quirino - 8 
 
Padrões de Projetos de Software com 
Java 
Caminho do Brilho: Saiba Quais Conteúdos Você Irá 
Aprender Conosco 
Onboarding 
Sejam bem-vindos à disciplina que é uma chave mestra para o seu sucesso no universo da 
Tecnologia da Informação: Padrões de Projetos de Software com Java. Aqui, estamos não só partilhando 
conhecimento, mas construindo juntos o alicerce de sua futura carreira. 
Esta disciplina é uma jornada enriquecedora, desenhada para desenvolver competências cruciais 
que unem teoria e prática, formação acadêmica e aplicação no mercado de trabalho. Vocês, alunos, estão 
no limiar de uma transformação significativa, aprendendo a engajar-se com padrões de projeto de software 
que são fundamentais na criação de sistemas robustos e eficientes. 
Os Padrões GOF, ou Gang of Four, são o coração do design de software orientado a objetos, e o 
domínio deles significa compreender a essência de soluções eficazes e elegantes no desenvolvimento de 
software. Já os Padrões GRASP trazem os princípios básicos para atribuir responsabilidades em objetos, 
fornecendo uma fundação sólida para o bom design de software. 
Além disso, com as tecnologias JPA e JEE, vocês mergulharão nos meandros do desenvolvimento 
Java para aplicações empresariais, o que representa uma habilidade altamente valorizada no mercado. 
Tudo isso está aqui para que vocês possam não apenas aspirar a melhorar suas condições de trabalho, 
mas efetivamente alcançá-las. 
O aprendizado nesta disciplina é contínuo e dinâmico, assim como o campo da Tecnologia da 
Informação. Cada aula, cada exercício prático, cada padrão que vocês dominarem será um degrau a mais 
na sua jornada para se tornarem profissionais qualificados e diferenciados. Vocês estão se preparando para 
serem líderes em um campo que está em constante evolução e que precisa de mentes aguçadas e criativas. 
Lembrando que estamos aqui não apenas para ensinar, mas para provocar e inspirar vocês a verem 
além. Queremos que percebam como o conhecimento adquirido é aplicável e vital para o sucesso 
profissional. Cada desafio superado aqui é uma pequena vitória, cada projeto concluído é um motivo de 
orgulho e cada conceito entendido é uma ferramenta poderosa em seu arsenal profissional. 
Celebrem cada conquista e saibam que cada novo padrão aprendido não é apenas mais um tópico 
coberto, mas um passo para se tornarem profissionais competentes e inovadores. Vocês são especiais por 
estar aqui, desfrutando do privilégio de aprender e crescer em um ambiente universitário. Sejam felizes 
nessa jornada de aprendizado e valorizem as transformações que o saber traz. 
Ao final desta disciplina, vocês estarão não só prontos para enfrentar os desafios da área de gestão 
com competência técnica, mas também para inspirar e educar outros, perpetuando a cadeia de inovação e 
excelência na área de TI. 
Bons estudos e que este seja o início de um caminho profissional repleto de realizações! 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 9 
 
Padrões GoF de Criação 
Descrição 
Padrões GoF de Projeto do grupo Criação: Template Method, Abstract Factory, Builder, Prototype e 
Singleton. 
Propósito 
Compreender os padrões de projeto ligados à criação de objetos em projetos orientados a objetos e 
identificar oportunidades para a sua aplicação são habilidades importantes para um projetista de software, 
pois, sem elas, as soluções geradas podem ser inflexíveis e dificultar a evolução de sistemas de software 
em prazo e custo aceitáveis. 
Preparação 
Antes de iniciar o conteúdo, é recomendado instalar em seu computador um programa que lhe 
permita elaborar modelos sob a forma de diagramas da UML (Linguagem Unificada de Modelagem). Nossa 
sugestão inicial é o Free Student License for Astah UML, usado nos exemplos deste estudo. Para isso, será 
necessário usar seu e-mail institucional para ativar a licença. 
Preencha os dados do formulário no site do software, envie e aguarde a liberação de sua licença em 
seu e-mail institucional. Ao receber a licença, siga as instruções do e-mail e instale o produto em seu 
computador. Os arquivos Astah com diagramas UML utilizados nesse conteúdo estão disponíveis para 
download. 
Sugestões de links adicionais de ferramentas livres para modelagem de sistemas em UML (UML 
Tools) podem ser encontradas em buscas na Internet. 
Além disso, recomendamos a instalação de um ambiente de programação em Java. O ambiente 
recomendado para iniciantesna View. 
Da mesma forma, a camada Controller é o melhor local para definir as regras de autorização para o 
uso de funcionalidades do sistema, tendo como base o perfil de um usuário autenticado. Com relação à 
autenticação, ela pode ser iniciada por uma tela de login na camada View, com a efetivação na camada 
Controller. Nos modelos atuais, é comum a geração de um token, mantendo a independência entre as 
camadas. 
Componentes Java para MVC 
Uma grande vantagem do MVC é o direcionamento do desenvolvedor e das ferramentas para as 
necessidades de cada camada. Com a divisão funcional, foram criados diversos frameworks, como o JSF 
(Java Server Faces), que define interfaces web na camada View, Spring ou EJB, para implementar as regras 
de negócio da camada Controller, e Hibernate, para a persistência ao nível da camada Model. 
O uso de camadas especializadas permite a divisão da equipe entre profissionais cujo perfil seja 
voltado para criação visual, negócios ou banco de dados. Veja na imagem: 
Padrões de Projetos de Software com Java 
Marcio Quirino - 154 
 
 
Os frameworks facilitam a manutenção e evolução do sistema, pois tendem a acompanhar as 
tecnologias que surgem ao longo do tempo, mas apenas empresas de grande porte e comunidades de 
código aberto são capazes de garantir as atualizações, não sendo raros os casos em que uma ferramenta 
menos conhecida é descontinuada. 
Em nosso contexto, a camada Model utiliza JPA, e como deve ser utilizada apenas pela camada 
Controller, é definida no mesmo projeto em que estão os componentes do tipo EJB. Note que a camada 
Controller oferece apenas as interfaces para os EJBs, com os dados sendo transitados na forma de 
entidades, sem acesso ao banco de dados, já que anotações não são serializáveis. 
Com a abordagem adotada, definimos o núcleo funcional e lógico de nosso sistema, sem a 
preocupação de satisfazer a qualquer tipo de tecnologia para construção de interfaces de usuário. A 
independência do núcleo garante que ele possa ser utilizado por diversas interfaces simultâneas, como: 
• SWING 
• HTTP 
• Web Services 
Padrões de Projetos de Software com Java 
Marcio Quirino - 155 
 
Sem que ocorra qualquer modificação nos componentes do tipo JPA ou EJB. 
Nos sistemas Java para web, um erro comum é definir os controladores no formato de servlets, pois 
as regras de negócio se confundem com as rotinas de conversão utilizadas entre o protocolo HTTP e as 
estruturas da linguagem Java. Essa abordagem errônea faz com que qualquer nova interface, como SWING, 
Web Services, ou até uma linha de comando, seja obrigada a solicitar os serviços utilizando o protocolo 
HTTP, algo que, de forma geral, não é uma exigência das regras de negócio dos sistemas. 
Considere que a entidade Produto, definida anteriormente, com uso de tecnologia JPA, seja criada 
no projeto ExemploEJB-ejb, no qual codificamos nosso session bean de teste com o nome Calculadora. 
Com a presença da entidade no projeto, podemos adicionar outro session bean do tipo Stateless, com o 
nome ProdutoGestor e uso de interface Local, para as operações cadastrais. 
@Local 
public interface ProdutoGestorLocal { 
 List obterTodos(); 
 void incluir(Produto p); 
} 
 
@Stateless 
public class ProdutoGestor implements ProdutoGestorLocal { 
 @Override 
 public List obterTodos() { 
 EntityManagerFactory emf = Persistence. 
 createEntityManagerFactory("ExemploSimplesJPAPU"); 
 EntityManager em = emf.createEntityManager(); 
 Query query = em.createNamedQuery("Produto.findAll"); 
 List lista = query.getResultList(); 
 em.close(); 
 return lista; 
 } 
 @Override 
 public void incluir(Produto p) { 
 EntityManagerFactory emf = Persistence. 
 createEntityManagerFactory("ExemploSimplesJPAPU"); 
 EntityManager em = emf.createEntityManager(); 
 try { 
 em.getTransaction().begin(); 
 em.persist(p); 
 em.getTransaction().commit(); 
 } catch (Exception e) { 
 em.getTransaction().rollback(); 
 } finally { 
 em.close(); 
 } 
} 
Repare que nossos códigos de exemplos anteriores foram aproveitados aqui, com leves adaptações, 
para que as operações sejam disponibilizadas por meio do session bean. 
Precisamos adicionar o arquivo persistence.xml, definido em nosso exemplo de JPA, ao diretório 
conf do projeto ExemploEJB-ejb, sem modificações, o que levará à utilização de controle transacional de 
forma local. 
Com nossas camadas Model e Controller completamente codificadas, podemos definir um Servlet, 
no projeto ExemploEJB-war, com o nome ServletListaProduto, que será parte da camada View do sistema, 
no modelo web. O objetivo do novo componente será a exibição da listagem dos produtos presentes na base 
de dados. 
@WebServlet(name = "ServletListaProduto", 
urlPatterns = {"/ServletListaProduto"}) 
public class ServletListaProduto extends HttpServlet { 
 @EJB 
 ProdutoGestorLocal facade; 
Padrões de Projetos de Software com Java 
Marcio Quirino - 156 
 
 
 protected void doGet(HttpServletRequest request, 
 HttpServletResponse response) 
 throws ServletException, IOException { 
 response.setContentType("text/html;charset=UTF-8"); 
 try (PrintWriter out = response.getWriter()) { 
 out.println(""); 
 facade.obterTodos().forEach(p -> { 
 out.println(" 
 " + p.getNome()); 
 }); 
 out.println(""); 
 } 
 } 
} 
No código, temos o atributo facade, do tipo ProdutoGestorLocal, utilizando a anotação EJB para 
injetar o acesso ao pool de Session Beans. Após configurar o acesso, invocamos o método obterTodos, na 
construção da resposta ao HTTP no modo GET, aceitando uma chamada como a apresentada a seguir. 
http://localhost:8080/ExemploEJB-war/ServletListaProduto 
Caso esteja tudo correto, teremos uma saída similar à que vemos a seguir, na tela do navegador. 
 
Implementação de MVC no ambiente Java 
A maioria dos sistemas possui diversas entidades de negócio que podem se relacionar. Esse 
relacionamento deve ser implementado nas camadas de banco e modelos. Contudo, esse tipo de situação 
costuma extrapolar as camadas mais baixas, reverberando até nas visualizações. 
Roteiro de prática 
Utilize o exemplo de sistema de produtos e adicione as camadas correspondentes à entidade 
"empresa". Um produto deverá possuir a apenas uma empresa. A empresa, por sua vez, pode possuir 
diversos produtos. Para executar os novos requisitos, siga as etapas: 
• Criar as tabelas e inserir os dados de empresas e produtos. 
• Crie a entidade "empresa" a partir do banco de dados. 
• Desenvolva um EJB para intermediar a comunicação com o modelo Produto. 
• Crie um Servlet responsável pela Listagem de produtos. 
Veja o resultado dessa prática. 
cria_registros.sql: 
CREATE TABLE EMPRESA ( 
CODIGO INT NOT NULL PRIMARY KEY, 
RAZAO_SOCIAL VARCHAR(50)); 
 
CREATE TABLE PRODUTO ( 
CODIGO INT NOT NULL PRIMARY KEY, 
Padrões de Projetos de Software com Java 
Marcio Quirino - 157 
 
NOME VARCHAR(50), 
 QUANTIDADE INTEGER, 
COD_EMPRESA INT NOT NULL); 
 
ALTER TABLE PRODUTO ADD FOREIGN KEY(COD_EMPRESA) 
REFERENCES EMPRESA(CODIGO); 
 
INSERT INTO EMPRESA VALUES (1,'SKY NET'); 
INSERT INTO EMPRESA VALUES (2,'MATRIX'); 
 
INSERT INTO PRODUTO VALUES (1,'Morango',200, 1); 
INSERT INTO PRODUTO VALUES (2,'Banana',1000, 1); 
INSERT INTO PRODUTO VALUES (3,'Manga',600, 2); 
Produto.java 
@Entity 
@Table(name = "PRODUTO") 
@NamedQueries({ 
 @NamedQuery(name = "Produto.findAll", query = "SELECT p FROM Produto p")}) 
public class Produto implements Serializable { 
 
 private static final long serialVersionUID = 1L;em Java é o Apache NetBeans, cujo instalador pode ser encontrado no site do 
ambiente, acessando o menu Download. Porém, antes de instalar o NetBeans, é necessário ter instalado o 
JDK (Java Development Kit) referente à edição Java SE (Standard Edition), que pode ser encontrado no site 
da Oracle Technology Network: Java SE - Downloads | Oracle Technology Network | Oracle. 
Os códigos com exemplos de aplicação dos padrões estão disponíveis ao longo do conteúdo em 
formato texto, bastando copiar para inserir no ambiente de programação. 
Introdução 
Os padrões GoF, do inglês “Gang of Four”, são padrões de projeto orientado a objetos divididos em 
três categorias: de criação, estruturais e comportamentais. São assim denominados por terem sido 
introduzidos pelos quatro autores do livro Design Patterns: Elements of Reusable Object-Oriented Software 
(GAMMA et al., 1994). 
Padrões de Projetos de Software com Java 
Marcio Quirino - 10 
 
 
Os padrões de projeto GoF de criação nos ajudam a construir sistemas independentes da forma com 
que os objetos são criados e representados. Quando criamos um objeto da classe B em um método da 
classe A por meio de um simples comando “new B()”, estabelecemos uma relação de dependência entre 
duas implementações, visto que, em Java, uma classe é uma implementação concreta de um conjunto de 
operações. Neste exemplo, portanto, a classe A é dependente da classe B. 
Em várias situações, entretanto, a criação de uma relação de dependência entre duas ou mais 
implementações torna o sistema inflexível, dificultando a sua evolução. Os princípios SOLID de Inversão 
de Dependências e Open Closed nos orientam a fazer as implementações dependerem de abstrações, 
especialmente em casos nos quais um módulo depende de um serviço que possa ter diferentes 
implementações. 
Princípios SOLID 
O acrônimo SOLID se refere, na programação orientada a objetos, a cinco princípios ou postulados de design, 
destinados a facilitar a compreensão, o desenvolvimento e a manutenção de software. São eles: 
• Single-responsibility (responsabilidade única); 
• Open-closed (aberto-fechado); 
• Liskov substitution (substituição de Liskov); 
• Interface segregation (segregação de interface); 
• Dependency inversion (inversão de dependência). 
Imagine um módulo que utilize um intermediador de pagamentos fornecido por uma empresa externa. 
Existe a chance de mudarmos a empresa fornecedora ou de termos de trabalhar com mais de uma empresa? 
Claro que sim! 
• Nesse caso, não é uma boa ideia fazer os módulos do nosso sistema dependerem da 
implementação de uma empresa específica. 
• Os módulos devem, portanto, depender de abstrações, de forma que seja possível trabalhar 
com diferentes intermediadores de pagamentos, sem haver necessidade de alterar os 
módulos clientes desse serviço. 
Entretanto, para utilizarmos as implementações específicas, precisamos instanciar objetos referentes 
a essas implementações. Como podemos instanciar esses objetos sem estabelecermos dependências 
indesejáveis? Esse é um dos problemas fundamentais tratados pelos padrões de projeto GoF do grupo 
Criação. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 11 
 
Neste conteúdo, você aprenderá os cinco padrões desse grupo: Factory Method, Abstract Factory, 
Builder, Prototype e Singleton. Esses padrões encapsulam o conhecimento sobre as classes concretas que 
o sistema utiliza e sobre como as instâncias dessas classes são criadas, permitindo que os demais módulos 
do sistema trabalhem com interfaces abstratas em lugar de implementações específicas. 
Projetos construídos com a aplicação correta desses padrões possuem maior flexibilidade em 
relação aos objetos criados, aos criadores desses objetos e a como e quando esses objetos são criados, 
facilitando a implementação de variações e evoluções do sistema. 
1. Padrão de projeto Factory Method 
Intenção do padrão Factory Method 
Factory Method é um padrão frequentemente utilizado na implementação de frameworks. 
Ele define uma interface para a criação de objetos, deixando para as subclasses a decisão sobre a 
classe específica a ser instanciada. 
Problema do padrão Factory Method 
Você conhece o problema que o padrão Factory Method busca resolver? 
Suponha que a sua tarefa seja implementar um método que remova os itens inválidos de uma 
coleção de itens de um pedido. Um item de pedido possui os seguintes atributos: 
1. A quantidade 
2. O preço unitário 
3. O produto solicitado 
Imagine que um item válido é aquele que tenha uma quantidade de 1 a 100. 
Veja a implementação da classe ItemPedido a seguir. 
Além dos atributos, das operações de acesso e do construtor, essa classe define a operação valor, 
que retorna o valor do item resultante da multiplicação da quantidade pelo preço unitário. 
1 public class ItemPedido { 
2 private Produto produto; 
3 private int quantidade; 
4 private int precoUnitarioEmCentavos; 
5 
6 public ItemPedido(Produto produto, int quantidade, int valorEmCentavos) { 
7 this.produto = produto; 
8 this.quantidade = quantidade; 
9 this.precoUnitarioEmCentavos = valorEmCentavos; 
10 } 
11 public int valor() { 
12 return quantidade * precoUnitarioEmCentavos; 
13 } 
14 public Produto getProduto() { 
15 return produto; 
16 } 
17 public void setProduto(Produto produto) { 
18 this.produto = produto; 
19 } 
20 public int getQuantidade() { 
21 return quantidade; 
22 } 
23 public void setQuantidade(int quantidade) { 
24 this.quantidade = quantidade; 
25 } 
26 public int getPrecoUnitarioEmCentavos() { 
27 return precoUnitarioEmCentavos; 
Padrões de Projetos de Software com Java 
Marcio Quirino - 12 
 
28 } 
29 public void setPrecoUnitarioEmCentavos(int precoUnitarioEmCentavos) { 
30 this.precoUnitarioEmCentavos = precoUnitarioEmCentavos; 
31 } 
32 } 
Uma primeira implementação da operação de remoção dos itens inválidos está listada a seguir: 
1 public void removerItensInvalidos(ArrayList‹ItemPedido› itens) { 
2 ArrayIterator‹ItemPedido› cursor = new ArrayIterator(itens); 
3 while (cursor.hasNext()) { 
4 ItemPedido item = cursor.next(); 
5 if (! isValido(item)) { 
6 cursor.remove(item); 
7 } 
8 } 
9 } 
10 
11 public boolean isValido(ItemPedido item) { 
12 return (item.getQuantidade() › 0 && item.getQuantidade() ‹ 100); 
13 } 
 
Nessa implementação, imagine que definimos uma classe ArrayIterator que implementa um cursor 
sobre os itens de pedido recebidos como parâmetro com as seguintes operações: 
A. hasNext 
✓ Que verifica se existe um próximo elemento no ArrayList ou se o cursor já está posicionado 
no último elemento. 
B. next 
✓ Que retorna o próximo elemento do ArrayList. Na primeira chamada, ele retorna o primeiro 
elemento da coleção. 
C. remove 
✓ Que remove um elemento da coleção. 
Você consegue identificar o principal problema dessa solução? 
Embora funcione, essa solução utiliza a classe ArrayList, criando um acoplamento da implementação 
com uma forma específica de organização dos itens. 
Suponha que os pedidos passem a ser organizados em um HashSet, por exemplo. O efeito negativo 
desse acoplamento fica evidente, pois teremos de modificar a implementação, uma vez que a forma de 
percurso em um HashSet é diferente daquela utilizada em um ArrayList. 
Poderíamos desenvolver outra versão específica para um HashSet, definindo uma classe 
HashSetIterator e implementando as mesmas operações da classe ArrayListIterator, mas com um algoritmo 
específico para o percurso e a manipulação dos elementos. 
O código a seguir apresenta a versão da operação removerItensInvalidos implementada a partir de 
um HashSet: 
1 public void removerItensInvalidos(HashSet‹ ItemPedido› itens) { 
2 HashSetIterator‹ItemPedido› cursor = new HashSetIterator(itens); 
3 while (cursor.hasNext()) { 
4 ItemPedido item = cursor.next(); 
5 if (! isValido(item)) { 
6 cursor.remove(item); 
7 } 
8 } 
9 } 
Imagine, agora,que houvesse diversos outros tipos de coleção. Você faria uma nova implementação 
para cada tipo específico de coleção? 
Padrões de Projetos de Software com Java 
Marcio Quirino - 13 
 
• Indo além, imagine que esse problema que você enfrentou em uma operação específica do 
sistema (isto é, remover itens de pedido inválidos) ocorra em dezenas de outras situações do 
mesmo sistema. O resultado será uma enorme replicação de código, que é um dos principais 
inimigos da evolução sustentável de um sistema. 
• Note que as duas implementações apresentadas são muito parecidas, diferindo apenas pelo 
tipo de coleção e do cursor criado. Considerando que todas as coleções implementam um 
tipo específico Collection, uma alternativa seria definir uma única operação 
removerltenslnvalidos e instanciar o cursor específico para a coleção recebida como 
parâmetro. 
O código a seguir apresenta essa implementação alternativa: 
1 public void removerItensInvalidos(Collection‹ItemPedido› itens) throws Exception { 
2 Iterator‹ItemPedido› cursor = null; 
3 if (itens instanceof ArrayList) 
4 cursor = new ArrayIterator((ArrayList) itens); 
5 else if (itens instanceof HashSet) 
6 cursor = new HashSetIterator((HashSet) itens); 
7 
8 if (cursor == null) 
9 throw new Exception("tipo da coleção de itens inválido"); 
10 
11 while (cursor.hasNext()) { 
12 ItemPedido item = cursor.next(); 
13 if (! isValido(item)) { 
14 cursor.remove(item); 
15 } 
16 } 
17 } 
Com essa solução, implementamos apenas uma operação removerItensInvalidos capaz de operar 
com um ArrayList ou com um HashSet. Agora, imagine que existissem vários outros tipos de coleção. 
Você consegue visualizar a enorme quantidade de comandos condicionais que deveriam ser 
adicionados? 
Portanto, esse código teria de ser modificado a cada novo tipo de implementação de coleção, 
acumulando uma quantidade significativa de expressões condicionais, o que é uma violação clara do 
princípio Open Closed – um dos princípios SOLID. 
Além disso, esse código apresenta estruturas baseadas em downcasting, o que é um indicativo de 
deficiência na estrutura da solução. O downcasting está presente na conversão da coleção de itens para 
ArrayList ou para HashSet, dependendo do tipo da coleção recebida como parâmetro. 
Comentário 
Perceba como essa implementação adiciona complexidade em relação à implementação anterior. Você deve 
ter sempre em mente que alta complexidade também é um dos principais inimigos da evolução sustentável de um 
sistema. 
O problema específico, portanto, consiste em implementar o método removerItensInvalidos, e todos 
os demais métodos nos quais você precise percorrer e interagir com uma coleção de objetos, de modo que 
ele funcione com qualquer forma de organização dessa coleção, sem que haja necessidade de recorrer a 
soluções baseadas em clonagem ou em estruturas condicionais complexas presentes nos exemplos 
apresentados. 
Atenção 
O problema mais geral resolvido pelo Factory Method é fazer com que um módulo cliente não precise instanciar 
diretamente uma dentre várias possíveis implementações de uma abstração, tornando-o, portanto, dependente apenas 
da abstração e não de suas implementações específicas. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 14 
 
Solução do padrão Factory Method 
O framework de estrutura de dados da linguagem Java implementa uma solução para esse problema 
por meio da aplicação do padrão Factory Method. 
As estruturas de dados são classes que implementam uma interface genérica chamada Collection. 
São exemplos de diferentes implementações dessa interface: 
A. ArrayList 
✓ Que representa estruturas como arrays. 
B. LinkedList 
✓ Que representa estruturas como listas encadeadas. 
C. HashSet 
✓ Que representa estruturas como conjuntos chave-valor. 
D. TreeSet 
✓ Que representa estruturas como conjuntos organizados em árvores de busca. 
A organização dessas classes está ilustrada, de forma simplificada, no diagrama de classes a seguir: 
 
A interface Collection define uma operação abstrata chamada iterator, que é implementada em cada 
estrutura de dados específica. Essa operação cria e retorna um objeto que implementa a interface Iterator. 
Atenção 
A interface Iterator define um cursor que possibilita a navegação em uma coleção de dados e a exclusão de 
elementos com as mesmas operações apresentadas nos exemplos anteriores, isto é, hasNext, next e remove. 
Você deve ter percebido que a implementação do percurso depende da forma com que os dados são 
organizados na coleção. A remoção de um elemento da coleção também é dependente da forma como seus 
elementos são estruturados. Isso significa que existe uma implementação da interface Iterator para cada 
classe que implementa a interface Collection, como você pode ver na imagem a seguir: 
Padrões de Projetos de Software com Java 
Marcio Quirino - 15 
 
 
Dessa forma, a operação iterator de ArrayList instancia um ArrayIterator, a de LinkedList instancia 
um ListIterator, e assim por diante, para cada coleção específica. Esse esquema é uma simplificação, para 
fins didáticos, das classes realmente implementadas na linguagem Java. 
Veja, no código a seguir, como você poderia implementar a operação removerItensInvalidos usando 
esse framework: 
1 public void removerItensInvalidos(Collection‹ItemPedido› itens) { 
2 Iterator‹ItemPedido› iterator = itens.iterator(); 
3 while (iterator.hasNext()) { 
4 ItemPedido item = iterator.next(); 
5 if (! isValido(item)) { 
6 itens.remove(item); 
7 } 
8 } 
9 } 
O comando itens.iterator() é uma chamada polimórfica a partir da interface Collection, que resulta na 
criação de uma das implementações específicas da interface Iterator. 
Portanto, se a Collection for um ArrayList, por exemplo, esse comando será executado pela classe 
ArrayList, que criará um ArrayIterator e retornará essa instância (cursor), a qual será referenciada pela 
variável iterator. 
Comentário 
Note que, nessa solução, a única responsabilidade da operação iterator é criar (fabricar) a instância da interface 
Iterator apropriada para a estrutura de dados. Esta é a ideia central do padrão Factory Method: definir uma operação 
“fábrica” na classe abstrata (Collection), deixando para cada subclasse (ArrayList, LinkedList, HashSet, TreeSet) a 
decisão da implementação específica da interface (Iterator) retornada pela fábrica. 
Na imagem a seguir, você pode observar que a estrutura geral da solução proposta pelo padrão 
Factory Method define quatro participantes: 
Padrões de Projetos de Software com Java 
Marcio Quirino - 16 
 
 
Do lado esquerdo, estão os produtos a serem fabricados. O participante Product corresponde ao tipo 
genérico do elemento a ser fabricado, enquanto ConcreteProduct corresponde a cada especialização do 
produto a ser fabricado. 
No exemplo das estruturas de dados, a interface Iterator desempenha o papel de Product, enquanto 
as classes ArrayIterator, ListIterator, KeyIterator e ValueIterator desempenham o papel de ConcreteProduct. 
Do lado direito, estão os criadores, isto é, as classes que são responsáveis pela instanciação dos 
produtos. O participante Creator define uma operação (factoryMethod) que retorna uma instância da 
interface genérica Product, enquanto ConcreteCreator representa cada implementação concreta de Creator 
responsável pela instanciação do ConcreteProduct específico. 
Portanto, no exemplo das estruturas de dados, a interface Collection corresponde ao participante 
Creator, e sua operação abstrata iterator é o factoryMethod. Já as classes ArrayList, LinkedList, HashSet e 
TreeSet desempenham o papel de ConcreteCreator e são responsáveis pela implementação da operação 
factoryMethod, na qual será feita a instanciação de um ArrayIterator, ListIterator, KeyIterator e ValueIterator 
(ConcreteProduct), respectivamente. 
Consequências e padrões relacionados ao Factory Method 
O padrão FactoryMethod permite que diferentes implementações de um mesmo serviço possam ser 
utilizadas por um cliente sem que seja necessário replicar códigos similares ou utilizar estruturas 
condicionais complexas, conforme ilustrado no exemplo anterior. 
Além disso, esse padrão possibilita a conexão de duas hierarquias paralelas representadas pelos 
participantes genéricos Creator e Product. 
O Factory Method é muito útil quando precisamos segregar uma hierarquia de objetos 
detentores de informações (objetos de domínio) dos diferentes algoritmos de manipulação dessas 
informações. 
Portanto, esse padrão pode ser aplicado em conjunto com o padrão Strategy, que tem como objetivo 
a separação de diferentes algoritmos dos objetos de domínio sobre os quais eles atuam. 
Template Method é outro padrão frequentemente utilizado em conjunto com o Factory Method. 
O Template Method é uma implementação genérica definida em uma superclasse que contém 
passos que podem ser especializados nas subclasses. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 17 
 
Um desses passos pode corresponder à criação de um objeto específico, que pode ser realizada 
pela aplicação do padrão Factory Method. 
2. Padrão de projeto Abstract Factory 
Intenção do padrão Abstract Factory 
Abstract Factory é um padrão que fornece uma interface para a criação de famílias de objetos 
relacionados ou dependentes, sem criar dependências entre o cliente e as classes concretas instanciadas. 
Problema do padrão Abstract Factory 
Imagine que você esteja trabalhando em uma implementação que tenha integrações com sistemas 
externos de diferentes organizações. Considere que os sistemas de cada organização externa enviem os 
mesmos tipos de mensagens em diferentes formatos, como, por exemplo, texto contendo campos de 
tamanho predefinido, XML, CSV, entre outros. Considere, ainda, que cada organização envie suas 
mensagens sempre no mesmo formato. 
O quadro a seguir apresenta um exemplo de integração com três organizações que enviam 
mensagens codificadas no formato especificado: 
Emissor Mensagem Formato 
Organização X Registrar Cliente XML 
Organização Y Registrar Cliente CSV 
Organização Z Registrar Cliente Campos de tamanho fixo 
Organização X Registrar Conta XML 
Organização Y Registrar Conta CSV 
Organização Z Registrar Conta Campos de tamanho fixo 
Quadro elaborado por Alexandre Correa 
Podemos definir um conjunto de classes responsáveis pela decodificação de mensagens de um 
formato específico em objetos independentes desse formato. 
A imagem a seguir apresenta a estrutura dessa solução: 
 
A interface RegistrarClienteDecoder representa um serviço genérico que traduz uma mensagem de 
registro de cliente, recebida em um formato qualquer, para um objeto da classe MsgRegistrarCliente, que 
corresponde à representação independente de formato da mensagem recebida. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 18 
 
As implementações dessa interface para cada formato específico são: 
A. XML 
✓ classe RegistrarClienteXMLDecoder 
B. Texto Fixo 
✓ classe RegistrarClienteTextoFixoDecoder 
C. CSV 
✓ classe RegistrarClienteCSVDecoder 
Atenção 
Para cada mensagem recebida pelo sistema (como, por exemplo, Registrar Cliente, Registrar Conta), deve ser 
criada uma estrutura de classes similar à apresentada. 
O código a seguir corresponde ao esqueleto de implementação da classe ServicoIntegracao: um 
exemplo de módulo que utiliza as classes de decodificação. 
As operações dessa classe representam as mensagens recebidas das diferentes organizações. 
A operação registrarCliente, por exemplo, recebe um texto com o conteúdo da mensagem enviada 
por uma origem. Essa origem é codificada em um texto (X, Y ou Z), representando as diferentes 
organizações. O texto da mensagem deve ser decodificado do formato específico em uma instância da 
classe MsgRegistrarCliente para seu posterior tratamento. 
1 public class ServicoIntegracao { 
2 public void registrarCliente (String textoMsg, String origem) { 
3 RegistrarClienteDecoder msgDecoder = null; 
4 
5 if (“X”.equals(origem)) { 
6 msgDecoder = new RegistrarClienteXMLDecoder(); 
7 } else if ("Y".equals(origem)) { 
8 msgDecoder = new RegistrarClienteCSVDecoder(); 
9 } else if ("Z".equals(origem)) { 
10 msgDecoder = new RegistrarClienteTextoFixoDecoder(); 
11 } 
12 MsgRegistrarCliente msg = msgDecoder.decode(textoMsg); 
13 ... 
14 // código para o tratamento da mensagem recebida 
15 } 
16 
17 public void registrarConta (String textoMsg, String origem) { 
18 RegistrarContaDecoder msgDecoder = null; 
19 
20 if (“X”.equals(origem)) { 
21 msgDecoder = new RegistrarContaXMLDecoder(); 
22 } else if ("Y".equals(origem)) { 
23 msgDecoder = new RegistrarContaCSVDecoder(); 
24 } else if ("Z".equals(origem)) { 
25 msgDecoder = new RegistrarContaTextoFixoDecoder(); 
26 } 
27 MsgRegistrarConta msg = msgDecoder.decode(textoMsg); 
28 ... 
29 // código para o tratamento da mensagem recebida 
30 } 
31 ... // operações para recepção e tratamento das demais mensagens 
32 } 
Você consegue identificar o problema dessa implementação da classe Servicolntegracao? 
Ela está acoplada com todos os tipos possíveis de decodificadores, concentrando toda a 
complexidade de resolução sobre o decodificador apropriado para traduzir uma mensagem vinda de 
determinada origem. 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 19 
 
Comentário 
Você deve ter percebido que, caso novos formatos e origens sejam adicionados, esse código terá de ser 
modificado, o que configura uma violação do princípio Open Closed, um dos princípios SOLID. 
Nesse exemplo, temos várias famílias de decodificadores de acordo com o formato da mensagem, 
como: decodificadores XML, CSV e Texto Fixo. 
1. Ao recebermos uma mensagem da origem X, por exemplo, sabemos que precisamos utilizar o 
conversor XML correspondente a essa mensagem, pois essa origem envia todas as suas 
mensagens no formato XML. 
2. Portanto, o problema tratado pelo padrão Abstract Factory consiste em isolar o cliente de uma 
família de produtos relacionados de suas implementações específicas, respondendo à seguinte 
pergunta: 
a. Como podemos remover todas as instanciações dos decodificadores da classe 
Servicolntegracao, criando uma solução genérica que permita que esse serviço trabalhe 
com novos formatos de decodificação sem que seu código precise ser alterado? 
Solução do padrão Abstract Factory 
A estrutura da solução proposta pelo padrão Abstract Factory está representada no diagrama de 
classes a seguir: 
 
Do lado direito, estão os vários produtos criados pelas fábricas. Cada tipo de produto é definido por 
uma interface genérica (AbstractProduct_A e AbstractProduct_B) e possui diversas implementações que 
definem os objetos específicos a serem criados pelas fábricas. Product_A1 e Product_A2, por exemplo, são 
implementações concretas de AbstractProduct_A. 
No problema apresentado anteriormente, a interface RegistrarClienteDecoder corresponde ao 
participante AbstractProduct_A, enquanto as classes RegistrarClienteXMLDecoder, 
RegistrarClienteTextoFixoDecoder e RegistrarClienteCSVDecoder representam os produtos concretos 
Product_A1, Product_A2 e Product_A3. 
Padrões de Projetos de Software com Java 
Marcio Quirino - 20 
 
De forma análoga, a interface RegistrarContaDecoder corresponde ao participante 
AbstractProduct_B, enquanto as classes RegistrarContaXMLDecoder, RegistrarContaTextoFixoDecoder e 
RegistrarContaCSVDecoder representam os produtos concretos Product_B1, Product_B2 e Product_B3. 
Do lado esquerdo, estão as fábricas. Cada fábrica é responsável por criar instâncias específicas de 
uma família definida por seus produtos abstratos. 
Dessa forma, a fábrica ConcreteFactory_1 é responsável por criar instâncias das classes Product_A1 
e Product_B1, enquanto a fábrica ConcreteFactory_2 é responsávelpor criar instâncias das classes 
Product_A2 e Product_B2. Portanto, Product_A1 e Product_B1 formam uma família de produtos, enquanto 
Product_A2 e Product_B2 formam outra família de produtos. 
Vamos ver como ficaria a nova solução para o serviço de integração com a utilização desse 
padrão? 
Primeiro, precisamos definir as fábricas. Podemos definir as famílias de decodificadores de acordo 
com o formato das mensagens, conforme ilustrado na imagem a seguir: 
 
A classe DecoderFactory representa o participante AbstractFactory do padrão, definindo uma 
interface genérica para a criação dos diversos decodificadores de mensagens. Cada especialização dessa 
classe corresponde ao participante ConcreteFactory do padrão, sendo responsável pela criação dos 
decodificadores correspondentes a um formato específico de mensagem (XML, CSV ou TextoFixo). 
A implementação dessa estrutura é apresentada a seguir. Note que a responsabilidade de cada 
fábrica é apenas instanciar a classe de um decodificador específico da família (XML, CSV etc.). A fábrica 
abstrata possui uma operação adicional (fabricaParaOrigem) que retorna a fábrica apropriada para 
determinada origem. Ela funciona como uma espécie de registro de todas as fábricas e suas respectivas 
origens. 
1 public abstract class DecoderFactory { 
2 public abstract RegistrarClienteDecoder createRegistrarClienteDecoder(); 
3 public abstract RegistrarContaDecoder createRegistrarContaDecoder(); 
4 
5 public static DecoderFactory fabricaParaOrigem(String origem) { 
6 if (“X”.equals(origem)) { 
7 return new XMLDecoderFactory(); 
8 } else if ("Y".equals(origem)) { 
9 return new CSVDecoderFactory(); 
10 } else if ("Z".equals(origem)) { 
11 return new TextoFixoDecoderFactory(); 
Padrões de Projetos de Software com Java 
Marcio Quirino - 21 
 
12 } 
13 } 
14 } 
15 
16 public class XMLDecoderFactory extends DecoderFactory { 
17 public RegistrarClienteDecoder createRegistrarClienteDecoder() { 
18 return new RegistrarClienteXMLDecoder(); 
19 } 
20 public RegistrarContaDecoder createRegistrarContaDecoder() { 
21 return new RegistrarContaXMLDecoder(); 
22 } 
23 } 
24 
25 public class CSVDecoderFactory extends DecoderFactory { 
26 public RegistrarClienteDecoder createRegistrarClienteDecoder() { 
27 return new RegistrarClienteCSVDecoder(); 
28 } 
29 public RegistrarContaDecoder createRegistrarContaDecoder() { 
30 return new RegistrarContaCSVDecoder(); 
31 } 
32 } 
Agora, vamos utilizar as fábricas para modificar a implementação das operações da classe 
ServicoIntegracao. 
Veja, no código a seguir, que a operação registrarCliente chama a operação fabricaParaOrigem, a 
partir da origem recebida como parâmetro, para obter a instância da fábrica apropriada para as mensagens 
recebidas dessa origem. 
Na sequência, a chamada para a operação createRegistrarClienteDecoder da fábrica cria o 
decodificador específico para a mensagem RegistrarCliente. 
1 public class ServicoIntegracao { 
2 public void registrarCliente (String textoMsg, String origem) { 
3 DecoderFactory decoderFactory = DecoderFactory.fabricaParaOrigem(origem); 
4 RegistrarClienteDecodermsgDecoder = 
decoderFactory.createRegistrarClienteDecoder(); 
5 MsgRegistrarCliente msg = msgDecoder.decode(textoMsg); 
6 ... 
7 // código para tratamento da mensagem MsgRegistrarCliente 
8 } 
9 
10 public void registrarConta (String textoMsg, String origem) { 
11 DecoderFactory decoderFactory = DecoderFactory.fabricaParaOrigem(origem); 
12 RegistrarContaDecoder msgDecoder = 
decoderFactory.createRegistrarContaDecoder(); 
13 MsgRegistrarConta msg = msgDecoder.decode(textoMsg); 
14 ... 
15 // código para tratamento da mensagem MsgRegistrarConta 
16 } 
17 
18 ... // código para demais mensagens 
19 } 
Note que esse código não precisará ser modificado para novos formatos de mensagem, pois bastará 
adicionar novos decodificadores e definir uma nova fábrica concreta. Além disso, a estrutura do código ficou 
muito mais enxuta e menos complexa. 
Esse padrão é utilizado, por exemplo, na implementação do framework AWT de interface com o 
usuário da linguagem Java. Os componentes visuais específicos de plataforma, como Windows e Motif, por 
exemplo, formam uma família de produtos (Button, Frame, Panel etc.). A classe Toolkit corresponde à fábrica 
abstrata que oferece operações de criação de cada componente visual. Cada plataforma é implementada 
em uma subclasse de Toolkit específica. 
 
Padrões de Projetos de Software com Java 
Marcio Quirino - 22 
 
AWT 
Abstract Window Toolkit é o toolkit gráfico original da linguagem de programação Java. 
Motif 
Interface gráfica padrão para usuários de sistema operacional Unix. 
Consequências e padrões relacionados ao Abstract Factory 
O padrão Abstract Factory promove o encapsulamento do processo de criação de objetos, isolando 
os clientes das implementações concretas, permitindo que os clientes sejam implementados pelo uso 
apenas de abstrações. 
Além disso, esse padrão promove a consistência entre produtos relacionados, isto é, produtos da 
mesma família que devem ser utilizados em conjunto. Entretanto, a introdução de novos produtos não é 
simples, pois exige mudança em todas as fábricas. 
Atenção 
Cada novo produto inserido exige a adição de uma nova operação de criação em cada fábrica da estrutura. 
O padrão Abstract Factory está relacionado com outros padrões de criação. Cada operação de 
criação é tipicamente implementada utilizando o padrão Factory Method. 
É possível configurar fábricas mais flexíveis utilizando o padrão Prototype. Cada fábrica concreta 
pode ser definida como um Singleton, já que, normalmente, apenas uma instância de uma fábrica específica 
precisa ser instanciada. 
No exemplo apresentado neste módulo, poderíamos ainda eliminar a duplicação de código similar 
presente nas operações registrarCliente e registrarConta, generalizando a fábrica para retornar objetos de 
um tipo genérico Decoder (ao invés de decodificadores específicos) e transformando essas operações em 
objetos por meio da aplicação de outros padrões, como o Command e o Template Method, por exemplo. 
Desafio 
Estude os padrões mencionados e tente modificar a estrutura do exemplo, aplicando-os na nova solução. 
3. Padrão de projeto Builder 
Intenção do padrão Builder 
Builder é um padrão que visa separar a construção de um objeto complexo de sua representação, 
de forma que o mesmo processo de construção possa construir diferentes representações desse objeto. 
Problema do padrão Builder 
Suponha que você esteja fazendo um sistema para uma corretora de valores mobiliários, e que esse 
sistema permita que o cliente exporte suas notas de negociação em diferentes formatos, tais como: XML, 
PDF ou XLS. 
Imagine que o processo de construção de qualquer representação da nota de negociação seja 
definido por três passos fundamentais: 
• Construir o cabeçalho da nota. 
• Listar as operações da nota. 
• Gerar o sumário com os totais e taxas de todas as operações do dia. 
Uma solução frequente utilizada para tal problema é definir todas as possíveis conversões em uma 
única classe, como ilustrado no código a seguir: 
Padrões de Projetos de Software com Java 
Marcio Quirino - 23 
 
1 public class ExportadorNota { 
2 public byte[] exportarNota(NotaNegociacao nota, String formato) { 
3 if (“XML”.equals(formato)) 
4 return gerarNotaXML(nota); 
5 else if (“PDF”.equals(formato)) 
6 return gerarNotaPDF(nota); 
7 else if (“XLS”.equals(formato)) 
8 return gerarNotaXLS(nota); 
9 } 
10 
11 private byte[] gerarNotaXML(NotaNegociacao nota) { 
12 // construir cabeçalho em XML 
13 // listar os itens da nota em XML 
14 // gerar sumário em XML 
15 // retornar conteúdo da nota no formato XML 
16 } 
17 
18 private byte[] gerarNotaPDF(NotaNegociacao nota) { 
19 // construir cabeçalho em PDF 
20 // listar os itens da nota em PDF 
21 // gerar sumário