Você está na página 1de 117

Padrões de Projeto de Software

0

PADRO ES DE PROJETOS DE SOFTWARE Apostila Jorge Juan Zavaleta Gavidia
PADRO ES DE
PROJETOS DE
SOFTWARE
Apostila
Jorge Juan Zavaleta Gavidia

Rio de Janeiro, fevereiro de 2013

PADRÕES DE PROJETOS DE SOFTWARE

Jorge Juan Zavaleta Gavidia

zavaleta.jorge@gmail.com

zavaleta@cos.ufrj.br

Termo de Uso

Todo o conteúdo desta apostila é propriedade de Jorge Zavaleta. A apostila pode ser utilizada livremente para estudo pessoal. Além disso, este material didático pode ser utilizado como material de apoio em cursos de ensino superior desde que seja citado explicitamente o proprietário do material assim como o livro E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Padrões de Projetos: Soluções reutilizáveis de software orientado a objetos. Porto Alegre - RS: Bookman, 2000.

É proibida qualquer utilização desse material que não se enquadre nas condições acima sem o prévio consentimento formal, por escrito do autor. O uso indevido está sujeito às medidas legais cabíveis.

Jorge Zavaleta

INDÍCE

1.

INTRODUÇÃO

1

1.1

O PARADIGMA DE ORIENTAÇÃO A OBJETOS

1

1.1.1 Classes

2

1.1.2 Objetos

3

1.1.3 Herança

4

1.1.4 Polimorfismo

5

1.2

A UML – A Linguagem de Modelagem Unificada (Unified Modeling Language)

6

1.2.1 Classes

6

1.2.2 Objetos

7

1.2.3 Estados

8

1.2.4 Pacotes

8

1.2.5 Relacionamentos

9

1.2.5.1 Associações

10

1.2.5.2 Generalizações

13

1.2.5.3 Dependências e Refinamentos

15

 

1.2.6

Mecanismos Gerais

16

1.3 PADRÕES DE PROJETOS DE SOFTWARE

17

1.4 O QUE É UM PADRÃO DE PROJETO?

18

1.5 COMO DESCREVER UM PADRÃO DE PROJETO

19

1.6 PRINCIPAIS PADRÕES DE PROJETO

20

1.6.1 Finalidade dos 23 padrões:

20

1.6.2 Como selecionar um padrão?

23

2.

PADRÕES GoF

24

2.1

PADRÕES DE CRIAÇÃO

24

2.1.1 Padrão Abstract Factory

24

2.1.2 Padrão Builder

29

2.1.3 Padrão Factory Method

32

2.1.4 Padrão Prototype

36

2.1.5 Padrão Singleton

40

2.2

PADRÕES ESTRUTURAIS

43

2.2.1 Padrão Adapter

43

2.2.2 Padrão Bridge

47

2.2.3 Padrão Composite

51

2.2.4 Padrão Decorator

56

2.2.6

Padrão Flyweight

62

2.2.7

Padrão Proxy

66

2.3 PADRÕES COMPORTAMENTAIS

70

2.3.1 Padrão Chain of Responsability

70

2.3.2 Padrão Command

74

2.3.3 Padrão Interpreter

78

2.3.4 Padrão Iterator

82

2.3.5 Padrão

Mediator

86

2.3.6 Padrão Memento

90

2.3.7 Padrão

Observer

93

2.3.8 Padrão State

96

2.3.9 Padrão Strategy

99

2.3.10 Padrão Template Method

103

2.3.11 Padrão Visitor

105

3. PADRÕES GRASP

111

4. ARQUITETURA EM CAMADAS

112

REFERÊNCIAS

113

Padrões de Projeto de Software

1

1. INTRODUÇÃO

Nesta seção apresenta-se uma introdução a um dos métodos de desenvolvimento de software baseado em padrões (os detalhes e as melhores práticas aprendidas por projetistas e usuários ao longo dos anos) e se apresenta os conceitos básicos que permeiam o uso das técnicas de orientação a objetos na programação, sempre utilizando a linguagem Java como motivador e a linguagem de modelagem (UML) que o suporta.

1.1 O PARADIGMA DE ORIENTAÇÃO A OBJETOS

Uma das atividades mais interessantes em Informática é certamente a busca constante de melhorias nas linguagens e técnicas para o desenvolvimento de software. Desta busca decorrem as transformações e evoluções das linguagens de programação, surgindo novas linguagens e novos paradigmas.

A Programação Orientada a Objetos (POO) utiliza os conceitos que aprendemos no jardim de infância: objetos e atributos, todos e partes, classes e membros. É difícil explicar por que demoramos tanto a aplicar estes conceitos à análise e especificação de sistemas de informações - talvez porque estivéssemos ocupados demais olhando o auge da análise estruturada para imaginar que havia alternativas.

Na compreensão do mundo real, as pessoas empregam constantemente três métodos de organização, sempre presentes em todos os seus pensamentos:

1. Diferenciação, baseado na experiência de cada um, de objetos particulares e seus atributos - quando distinguem uma árvore, e seu tamanho ou relações espaciais, dos outros objetos;

2. Distinção entre objetos como um todo e entre suas partes componentes, por exemplo, quando separam uma árvore dos seus galhos; e

3. Formação de, e distinção entre, as diferentes classes de objetos - por exemplo, quando formam uma classe de todas as árvores, outra classe de todas as rochas e distinguem-nas.

Programação Orientada a Objetos é a programação implementada pelo envio de mensagens a objetos. Cada objeto irá responder às mensagens conhecidas por este, e cada objeto poderá enviar mensagens a outros, para que sejam atendidas, de maneira que ao final do programa, todas as mensagens enviadas foram respondidas, atingindo-se o objetivo do programa. Programação Orientada a Objetos, técnicas e artefatos ditos “orientados a objetos” incluem linguagens, sistemas, interfaces, ambientes de desenvolvimento, bases de dados, etc.

O paradigma de orientação a objetos é centrado no conceito de objeto. Tudo esta focado nele. Escrevo código organizado em torno de objetos, não de funções (SHALLOWAY e TROTT, 2004). Objetos são instâncias de classes, que determinam qual informação um objeto contém e como ele pode manipulá-la.

Padrões de Projeto de Software

2

Um dos grandes diferenciais da programação orientada a objetos em relação a outros paradigmas de programação que também permitem a definição de estruturas e operações sobre as mesmas estão no conceito de herança, mecanismo através do qual as definições existentes podem ser facilmente estendidas. Juntamente com a herança deve ser enfatizada a importância do polimorfismo, que permite selecionar funcionalidades que um programa irá utilizar de forma dinâmica, durante sua execução.

1.1.1 Classes

A definição de classes e seus inter-relacionamentos é o principal resultado da etapa de projeto de software. Em geral, esse resultado é expresso em termos de alguma linguagem de modelagem, tal como UML.

Uma classe é um gabarito para a definição de objetos. Através da definição de uma classe, descreve-se que propriedades ou atributos que o objeto terá. Além da especificação de atributos, a definição de uma classe descreve também qual o comportamento de objetos da classe, ou seja, que funcionalidades podem ser aplicadas a objetos da classe. Essas funcionalidades são descritas através de métodos. Um método nada mais é que o equivalente a um procedimento ou função, com a restrição que ele manipula apenas suas variáveis locais e os atributos que foram definidos para a classe.

Uma vez que estejam definidas quais serão as classes que irão compor uma aplicação, assim como qual deve ser sua estrutura interna e comportamento, é possível criar essas classes em Java.

Na UML, a representação para uma classe no diagrama de classes é tipicamente expressa na forma gráfica, como mostrado na Figura a seguir.

na forma gráfica, como mostrado na Figura a seguir. Como se observa na figura, a especificação

Como se observa na figura, a especificação de uma classe é composta por três regiões: o nome da classe, o conjunto de atributos da classe e o conjunto de métodos da classe. O nome da classe é um identificador para a classe, que permite referenciá-la posteriormente, por exemplo, no momento da criação de um objeto.

O conjunto de atributos descreve as propriedades da classe. Cada atributo é identificado por um nome e tem um tipo associado. Em uma linguagem de programação orientada a objetos pura, o tipo é o nome de uma classe. Na prática, a maior parte das linguagens de programação orientada a objetos oferecem um grupo de tipos primitivos, como inteiro, real e caráter, que podem ser usados na descrição de atributos. O atributo

Padrões de Projeto de Software

3

pode ainda ter um valor default opcional, que especifica um valor inicial para o atributo.

Os métodos definem as funcionalidades da classe, ou seja, o que será possível fazer com objetos dessa classe. Cada método é especificado por uma assinatura, composta por um identificador para o método (o nome do método), o tipo para o valor de retorno e sua lista de argumentos, sendo cada argumento identificado por seu tipo e nome.

Através do mecanismo de sobrecarga (overloading), dois métodos de uma classe podem ter o mesmo nome, desde que suas assinaturas sejam diferentes. Tal situação não gera conflito, pois o compilador é capaz de detectar qual método deve ser escolhido a partir da análise dos tipos dos argumentos do método. Nesse caso, diz-se que ocorre a ligação prematura (early binding) para o método correto.

O modificador de visibilidade pode estar presente tanto para atributos como para métodos. Em princípio, três categorias de visibilidade podem ser definidas:

+: Público: denotado em UML pelo símbolo (+), nesse caso, o atributo ou método de um objeto dessa classe pode ser acessado por qualquer outro objeto (visibilidade externa total);

-: Privativo: denotado em UML pelo símbolo (-), nesse caso, o atributo ou método de um objeto dessa classe não pode ser acessado por nenhum outro objeto (nenhuma visibilidade externa);

#: Protegido: denotado em UML pelo símbolo (#), nesse caso, o atributo ou método de um objeto dessa classe poderá ser acessado apenas por objetos de classes que sejam derivadas dessa através do mecanismo de herança.

1.1.2 Objetos

Objetos são instâncias de classes. É através deles que (praticamente) todo o processamento ocorre em sistemas implementados com linguagens de programação orientadas a objetos. O uso racional de objetos, obedecendo aos princípios associados à sua definição conforme estabelecido no paradigma de desenvolvimento orientado a objetos, é chave para o desenvolvimento de sistemas complexos e eficientes.

Um objeto é um elemento que representa, no domínio da solução, alguma entidade (abstrata ou concreta) do domínio de interesse do problema sobre análise. Objetos similares são agrupados em classes.

No paradigma de orientação a objetos, tudo pode ser potencialmente representado como um objeto. Sob o ponto de vista da programação orientada a objetos, um objeto não é muito diferente de uma variável normal. Por exemplo, quando se define uma variável do tipo int em uma linguagem de programação como C ou Java, essa variável tem:

Um espaço em memória para registrar o seu estado (valor);

Padrões de Projeto de Software

4

Um conjunto de operações que podem ser aplicadas a ela, através dos operadores definidos na linguagem que podem ser aplicados a valores inteiros.

Da mesma forma, quando se cria um objeto, esse objeto adquire um espaço em memória para armazenar seu estado (os valores de seu conjunto de atributos, definidos pela classe) e um conjunto de operações que podem ser aplicadas ao objeto (o conjunto de métodos definidos pela classe). Um programa orientado a objetos é composto por um conjunto de objetos que interagem através de “trocas de mensagens”. Na prática, essa troca de mensagem traduz-se na aplicação de métodos a objetos.

As técnicas de programação orientada a objetos recomendam que a estrutura de um objeto e a implementação de seus métodos devem ser tão privativos como possível. Normalmente, os atributos de um objeto não devem ser visíveis externamente. Da mesma forma, de um método deve ser suficiente conhecer apenas sua especificação, sem necessidade de saber detalhes de como a funcionalidade que ele executa é implementada.

Encapsulação é o princípio de projeto pelo qual cada componente de um programa deve agregar toda a informação relevante para sua manipulação como uma unidade (uma cápsula). Aliado ao conceito de ocultamento de informação é um poderoso mecanismo da programação orientada a objetos. Ocultamento da informação é o princípio pelo qual cada componente deve manter oculta sob sua guarda uma decisão de projeto única. Para a utilização desse componente, apenas o mínimo necessário para sua operação deve ser revelado (tornado público).

Na orientação a objetos, o uso da encapsulação e ocultamento da informação recomenda que a representação do estado de um objeto deve ser mantida oculta. Cada objeto deve ser manipulado exclusivamente através dos métodos públicos do objeto, dos quais apenas a assinatura deve ser revelada.

O conjunto de assinaturas dos métodos públicos da classe constitui sua interface

operacional. Dessa forma, detalhes internos sobre a operação do objeto não são conhecidos, permitindo que o usuário do objeto trabalhe em um nível mais alto de abstração, sem preocupação com os detalhes internos da classe. Essa facilidade permite

simplificar a construção de programas com funcionalidades complexas, tais como interfaces gráficas ou aplicações distribuídas.

1.1.3 Herança

O conceito de encapsular estrutura e comportamento em um tipo não é exclusivo da

orientação a objetos; particularmente, a programação por tipos abstratos de dados segue esse mesmo conceito. O que torna a orientação a objetos única é o conceito de herança.

Herança é um mecanismo que permite que características comuns a diversas classes sejam fatoradas em uma classe base, ou superclasse. A partir de uma classe base, outras classes podem ser especificadas. Cada classe derivada ou subclasse

Padrões de Projeto de Software

5

apresenta as características (estrutura e métodos) da classe base e acrescenta a elas o que for definido de particularidade para ela.

Há várias formas de relacionamentos em herança:

Extensão: a subclasse estende a superclasse, acrescentando novos membros (atributos e/ou métodos). A superclasse permanece inalterada, motivo pelo qual este tipo de relacionamento é normalmente referenciado como herança estrita.

Especificação: a superclasse especifica o que uma subclasse deve oferecer, mas não implementa nenhuma funcionalidade. Diz-se que apenas a interface (conjunto de especificação dos métodos públicos) da superclasse é herdada pela subclasse.

Combinação de extensão e especificação: a subclasse herda a interface e uma implementação padrão de (pelo menos alguns de) métodos da superclasse. A subclasse pode então redefinir métodos para especializar o comportamento em relação ao que é oferecido pela superclasse, ou ter que oferecer alguma implementação para métodos que a superclasse tenha declarado, mas não implementado. Normalmente, este tipo de relacionamento é denominado herança polimórfica.

A última forma é, sem dúvida, a que mais ocorre na programação orientada a objetos. Algumas modelagens introduzem uma forma de herança conhecida como contração. Contração é uma variante de herança onde a subclasse elimina métodos da superclasse com o objetivo de criar uma “classe mais simples”. A eliminação pode ocorrer pela redefinição de métodos com corpo vazio. O problema com este mecanismo

é que ele viola o princípio da substituição, segundo o qual uma subclasse deve poder

ser utilizada em todos os pontos onde a superclasse poderia ser utilizada. Se a contração parece ser uma solução adequada em uma hierarquia de classes, provavelmente a hierarquia deve ser reanalisada para detecção de inconsistências. De modo geral, o mecanismo de contração deve ser evitado.

1.1.4 Polimorfismo

Polimorfismo é o princípio pelo qual duas ou mais classes derivadas de uma

mesma superclasse podem invocar métodos que têm a mesma identificação (assinatura), mas comportamentos distintos, especializados para cada classe derivada, usando para tanto uma referência a um objeto do tipo da superclasse. Esse mecanismo

é fundamental na programação orientada a objetos, permitindo definir funcionalidades que operem genericamente com objetos, abstraindo-se de seus detalhes particulares quando esses não forem necessários.

Para que o polimorfismo possa ser utilizado, é necessário que os métodos que estejam sendo definidos nas classes derivadas tenham exatamente a mesma assinatura do método definido na superclasse; nesse caso, está sendo utilizado o mecanismo de redefinição de métodos (overriding). Esse mecanismo de redefinição é muito diferente do mecanismo de sobrecarga de métodos, onde as listas de argumentos são diferentes.

Padrões de Projeto de Software

6

No caso do polimorfismo, o compilador não tem como decidir qual o método que será utilizado se o método foi redefinido em outras classes, afinal, pelo princípio da substituição um objeto de uma classe derivada pode estar sendo referenciado como sendo um objeto da superclasse. Se esse for o caso, o método que deve ser selecionado é o da classe derivada e não o da superclasse.

Dessa forma, a decisão sobre qual dos métodos método que deve ser selecionado, de acordo com o tipo do objeto, pode ser tomada apenas em tempo de execução, através do mecanismo de ligação tardia. O mecanismo de ligação tardia também é conhecido pelos termos em inglês late binding, dynamic binding ou ainda runtime binding.

1.2 A UML – A Linguagem de Modelagem Unificada (Unified Modeling Language)

A UML é uma linguagem visual utilizada para modelar softwares baseados no

paradigma de orientação a objeto. É uma linguagem de modelagem de propósito geral que pode ser aplicada a todos os domínios de aplicação. Essa linguagem tornou-se, nos últimos anos, a linguagem padrão de modelagem adotada internacionalmente pela indústria de engenharia do software (GUEDES, 2011).

A UML é uma linguagem visual (isto é, uma notação de desenho com semântica)

utilizada para criar modelos de programas, ou quais podem ser entendidos como uma representação diagramática dos programas. Nela podemos ver os relacionamentos entre os objetos no código (SHALLOWAY e TROTT, 2004).

A UML possui diferentes tipos de diagramas, alguns para análise, outros para projetos e outros ainda para implementação. Cada diagrama, dependendo de seu propósito, mostra os relacionamentos entre diferentes conjuntos de entidades.

1.2.1 Classes

Uma classe é a descrição de um tipo de objeto. Todos os objetos são instâncias de classes, onde a classe descreve as propriedades e comportamentos daquele objeto. Objetos só podem ser instanciados de classes. Usam-se classes para classificar os objetos que identificamos no mundo real. Uma classe pode ser a descrição de um objeto em qualquer tipo de sistema.

ser a descrição de um objeto em qualquer tipo de sistema. Em UML as classes são
ser a descrição de um objeto em qualquer tipo de sistema. Em UML as classes são

Em UML as classes são representadas por um retângulo dividido em três compartimentos: o compartimento de nome, que conterá apenas o nome da classe modelada, o de atributos, que possuirá a relação de atributos que a classe possui em sua

Padrões de Projeto de Software

7

estrutura interna, e o compartimento de operações, que serão os métodos de manipulação dedados e de comunicação de uma classe com outras do sistema. A sintaxe usada em cada um destes compartimentos é independente de qualquer linguagem de programação, embora possam ser usadas outras sintaxes.

Exemplo: classe turma

Atributos: sala, horário;

Operações: obter local, adicionar estudante, obter horário;

1.2.2 Objetos

Um objeto é um elemento que podemos manipular e acompanhar seu comportamento, criar, destruir, etc. Um objeto existe no mundo real. Pode ser uma parte de qualquer tipo de sistema, por exemplo, uma máquina, uma organização, ou negócio. Existem objetos que não encontramos no mundo real, mas que podem ser vistos de derivações de estudos da estrutura e comportamento de outros objetos do mundo real.

Um objeto num sistema possui três propriedades: estado, comportamento e identidade:

Estado: definido pelo conjunto de propriedades do objeto (os atributos) e de suas relações com os outros objetos. É algo que muda com o tempo, por exemplo, um objeto turma pode estar no estado aberto ou fechado. Inicia no estado aberto e fecha quando 10 alunos fazem inscrição.

Comportamento: como um objeto responde às solicitações dos outros e tudo mais o que um objeto é capaz de fazer. É implementado por um conjunto de operações. Ex. objeto turma pode ter operações acrescentar aluno ou suprimir aluno.

Identidade: significa que cada objeto é único no sistema. Por exemplo, o objeto turma Tecno-OO manhã é diferente do objeto Tecno-OO tarde.

objeto turma Tecno-OO manhã é diferente do objeto Tecno-OO tarde . © Jorge Zavaleta, 2013 zavaleta.jorge@gmail.com
objeto turma Tecno-OO manhã é diferente do objeto Tecno-OO tarde . © Jorge Zavaleta, 2013 zavaleta.jorge@gmail.com

Padrões de Projeto de Software

8

Em UML um objeto é mostrado como uma classe só que seu nome (do objeto) é sublinhado, e o nome do objeto pode ser mostrado opcionalmente precedido do nome da classe.

1.2.3 Estados

Todos os objetos possuem um estado que significa o resultado de atividades executadas pelo objeto, e é normalmente determinada pelos valores de seus atributos e ligações com outros objetos.

Um objeto muda de estado quando acontece algo, o fato de acontecer alguma coisa como objeto é chamado de evento. Através da análise da mudança de estados dos tipos de objetos de um sistema, podemos prever todos os possíveis comportamentos dos objetos de acordo com os eventos que o mesmo possa sofrer.

objetos de acordo com os eventos que o mesmo possa sofrer. Um estado, em sua notação,

Um estado, em sua notação, pode conter três compartimentos. O primeiro mostra o nome do estado. O segundo é opcional e mostra a variável do estado, onde os atributos do objeto em questão podem ser listados e atualizados. Os atributos são aqueles mostrados na representação da classe, e em algumas vezes, podem ser mostradas também as variáveis temporárias, que são muito úteis em diagramas de estado, já que através da observância de seus valores podemos perceber a sua influência na mudança de estados de um objeto.

O terceiro compartimento é opcional e é chamado de compartimento de atividade,

onde eventos e ações podem ser listados. Três eventos padrões podem ser mostrados no compartimento de atividades de um estado: entrar, sair e fazer. O evento entrar pode ser usado para definir atividades no momento em que o objeto entra naquele estado.

O evento sair define atividades que o objeto executa antes de passar para o próximo

estado e o evento fazer define as atividades do objeto enquanto se encontra naquele estado.

1.2.4 Pacotes

Pacote é um mecanismo de agrupamento, onde todos os modelos de elementos podem ser agrupados. Em UML, um pacote é definido como: “Um mecanismo de propósito geral para organizar elementos semanticamente relacionados em grupos.” Todos os modelos de elementos que são ligados ou referenciados por um pacote são chamados de “Conteúdo do pacote”.

Padrões de Projeto de Software

9

Um pacote possui vários modelos de elementos, e isto significa que estes não podem ser incluídos em outros pacotes.

que estes não podem ser incluídos em outros pacotes. Pacotes podem importar modelos de elementos de

Pacotes podem importar modelos de elementos de outros pacotes. Quando um modelo de elemento é importado, refere-se apenas a o pacote que possui o elemento. Na grande maioria dos casos, os pacotes possuem relacionamentos com outros pacotes. Embora estes não possuam semânticas definidas para suas instâncias. Os relacionamentos permitidos entre pacotes são de dependência, refinamento e generalização (herança).

O pacote tem uma grande similaridade com a agregação (relacionamento que será tratado mais na frente). O fato de um pacote ser composto de modelos de elementos cria uma agregação de composição. Se este for destruído, todo o seu conteúdo também será.

1.2.5 Relacionamentos

Os relacionamentos ligam as classes/objetos entre si criando relações lógicas entre estas entidades. Os relacionamentos podem ser dos seguintes tipos:

Associação: É uma conexão entre classes, e também significa que é uma conexão entre objetos daquelas classes. Em UML, uma associação é definida comum relacionamento que descreve uma série de ligações, onde a ligação é definida como a semântica entre as duplas de objetos ligados.

Generalização: É um relacionamento de um elemento mais geral e outro mais específico. O elemento mais específico pode conter apenas informações adicionais. Uma instância (um objeto é uma instância de uma classe) do elemento mais específico pode ser usada onde o elemento mais geral seja permitido.

Dependência e Refinamentos: Dependência é um relacionamento entre elementos, um independente e outro dependente. Uma dependência é representada graficamente por uma seta aberta, com linha tracejada.

representada graficamente por uma seta aberta, com linha tracejada . © Jorge Zavaleta, 2013 zavaleta.jorge@gmail.com

Padrões de Projeto de Software

10

A seta que representa a relação de dependência sai do item dependente e aponta para o item independente.

sai do item dependente e aponta para o item independente. Uma modificação é um elemento independente

Uma modificação é um elemento independente afetará diretamente elementos dependentes do anterior. Refinamento é um relacionamento entre duas descrições de uma mesma entidade, mas em níveis diferentes de abstração.

1.2.5.1 Associações

Uma associação representa que duas classes possuem uma ligação (link) entre elas, significando, por exemplo, que elas “conhecem uma a outra”, “estão conectadas com”, “para cada X existe um Y” e assim por diante. Classes e associações são muito poderosas quando modeladas em sistemas complexos.

Associação Normal: O tipo mais comum de associação é apenas uma conexão entre classes. É representada por uma linha sólida entre duas classes. A associação possui um nome (junto à linha que representa a associação), normalmente um verbo, mas substantivos também são permitidos.

um verbo, mas substantivos também são permitidos. No exemplo gráfico acima vemos um relacionamento por

No exemplo gráfico acima vemos um relacionamento por associação entre as classes Funcionário e Departamento.

Pode-se também colocar uma seta no final da associação indicando que esta só pode ser usada para o lado onde a seta aponta. Mas associações também podem possuir dois nomes, significando um nome para cada sentido da associação.

Para expressar a multiplicidade entre os relacionamentos, um intervalo indica

quantos objetos estão relacionados no link. O intervalo pode ser de zero para um (0 1), *

11)

zero para vários (0

e assim por diante. É também possível expressar uma série de números como (1, 4,

12). 6

para um (1

Se não for descrito nenhuma multiplicidade, então é considerado o padrão de um

ou apenas*), um para vários (1

*),

dois (2), cinco para 11 (5

1

ou apenas 1).

Associação Recursiva: É possível conectar uma classe a ela mesma através de uma associação e que ainda representa semanticamente a conexão entre dois objetos, mas os

Padrões de Projeto de Software

11

objetos conectados são da mesma classe. Uma associação deste tipo é chamada de associação recursiva.

deste tipo é chamada de associação recursiva. Associação Qualificada : Associações qualificadas são

Associação Qualificada: Associações qualificadas são usadas com associações de

ou vários para vários (*). O “qualificador” (identificador da

associação qualificada) especifica como um determinado objeto no final da associação “n” é identificado, e pode ser visto como um tipo de chave para separar todos os objetos na associação. O identificador é desenhado como uma pequena caixa no final da associação junto à classe de onde a navegação deve ser feita.

um para vários (1

*)

de onde a navegação deve ser feita. um para vários (1 *) Associação Exclusiva : Em

Associação Exclusiva: Em alguns modelos nem todas as combinações são válidas, e isto pode causar problemas que devem ser tratados. Uma associação exclusiva é uma restrição em duas ou mais associações. Ela especifica que objetos de uma classe podem participar de no máximo uma das associações em um dado momento. Uma associação exclusiva é representada por uma linha tracejada entre as associações que são partes da associação exclusiva, com a especificação “{ou}” sobre a linha tracejada.

com a especificação “{ou}” sobre a linha tracejada. No diagrama acima um contrato não pode se

No diagrama acima um contrato não pode se referir a uma pessoa e a uma empresa ao mesmo tempo, significando que o relacionamento é exclusivo a somente uma das duas classes.

Padrões de Projeto de Software

12

Associação Ordenada: As associações entre objetos podem ter uma ordem implícita. O padrão para uma associação é desordenada (ou sem nenhuma ordem específica). Mas uma ordem pode ser especificada através da associação ordenada. Este tipo de associação pode ser muito útil em casos como este: janelas de um sistema têm que ser ordenadas na tela (uma está no topo, uma está no fundo e assim por diante). A associação ordenada pode ser escrita apenas colocando “{ordenada}” junto à linha de associação entre as duas classes.

Associação de Classe: Uma classe pode ser associada à outra associação. Este tipo de associação não é conectada a nenhuma das extremidades da associação já existente, mas na própria linha da associação. Esta associação serve para se adicionar informações extras a associação já existente.

A associação da classe Fila com a associação das classes Cliente e Processo pode ser estendida com operações de adicionar processos na fila, para ler e remover da fila e de ler o seu tamanho. Se operações ou atributos são adicionados à associação, ela deve ser mostrada como uma classe.

à associação, ela deve ser mostrada como uma classe. Associação Ternária : Mais de duas classes

Associação Ternária: Mais de duas classes podem ser associadas entre si, a associação ternária associa três classes. Ela é mostrada como um losango grande (diamante) e ainda suporta uma associação de classe ligada a ela, se traçaria, então, uma linha tracejada a partir do losango para a classe onde seria feita a associação ternária.

para a classe onde seria feita a associação ternária. No exemplo acima a associação ternária especifica

No exemplo acima a associação ternária especifica que um cliente poderá possuir 1oumais contratos e cada contrato será composto de1 ou várias regras contratuais.

Padrões de Projeto de Software

13

Agregação: A agregação é um caso particular da associação. A agregação indica que uma das classes do relacionamento é uma parte, ou está contida em outra classe. As palavras chaves usadas para identificar uma agregação são: “consiste em”, “contém”, “é parte de”. A agregação também é conhecida como relacionamento “tem-um”, ou seja, dizemos que um objeto gerado da classe “todo” tem um objeto gerado da classe “parte”.

“ todo ” tem um objeto gerado da classe “ parte ”. Existem tipos especiais de

Existem tipos especiais de agregação que são as agregações compartilhadas e as compostas.

Agregação Compartilhada: É dita compartilhada quando uma das classes é uma parte, ou está contida na outra, mas esta parte pode estar contida nas outras várias vezes e num mesmo momento.

estar contida nas outras várias vezes e num mesmo momento. No exemplo acima uma pessoa pode

No exemplo acima uma pessoa pode ser membro de um time ou vários times em determinado momento.

Agregação de Composição: É uma agregação onde uma classe que está contida na outra “vive” e constitui a outra. Se o objeto da classe que contém for destruído, as classes da agregação de composição serão destruídas juntamente já que as mesmas fazem parte da outra.

juntamente já que as mesmas fazem parte da outra. 1.2.5.2 Generalizações A generalização é um

1.2.5.2 Generalizações

A generalização é um relacionamento hierárquico entre classes, isto é, a generalização é um relacionamento entre um elemento geral e outro mais específico. O elemento mais específico possui todas as características do elemento geral e contém ainda mais particularidades. Um objeto mais específico pode ser usado como uma

Padrões de Projeto de Software

14

instância do elemento mais geral. A generalização, também chamada de herança, permite a criação de elementos especializados em outros.

A classe de maior nível hierárquico é conhecida como superclasse, enquanto a

outra classe da relação é conhecida como subclasse. Graficamente uma generalização é

representada por uma linha contínua com uma seta em branco apontando sempre a superclasse.

com uma seta em branco apontando sempre a superclasse . Existem alguns tipos de generalizações que

Existem alguns tipos de generalizações que variam em sua utilização a partir da situação. São elas: generalização normal e restrita. As generalizações restritas se dividem em generalização de sobreposição, disjuntiva, completa e incompleta.

Generalização Normal: Na generalização normal a classe mais específica, chamada de subclasse, herda tudo da classe mais geral, chamada de superclasse. Os atributos, operações e todas as associações são herdados.

operações e todas as associações são herdados. public class ContaCorrente {} public class Poupança extends

public class ContaCorrente {} public class Poupança extends ContaCorrente {}

Uma classe pode ser tanto uma subclasse quanto uma superclasse, se ela estiver numa hierarquia de classes que é um gráfico onde as classes estão ligadas através de generalizações.

onde as classes estão ligadas através de generalizações. A generalização normal é representada por uma linha

A generalização normal é representada por uma linha entre as duas classes que

fazem o relacionamento, sendo que se coloca uma seta no lado da linha onde se encontra a superclasse indicando a generalização.

Generalização Restrita: Uma restrição aplicada a uma generalização especifica informações mais precisas sobre como a generalização deve ser usada e estendida no futuro. As restrições a seguir definem mais generalizações restritas com mais de uma subclasse:

Generalizações de Sobreposição e Disjuntiva: Generalização de sobreposição significa que quando subclasses herdam de uma superclasse por sobreposição,

Padrões de Projeto de Software

15

novas subclasses destas podem herdar de mais de uma subclasse. A generalização disjuntiva é exatamente o contrário da sobreposição e a generalização é utilizada como padrão.

e a generalização é utilizada como padrão. • Generalizações Completas e Incompletas : Uma

Generalizações Completas e Incompletas: Uma restrição simbolizando que uma generalização é completa significa que todas as subclasses já foram especificadas, e não existe mais possibilidade de outra generalização a partir daquele ponto. A generalização incompleta é exatamente o contrário da completa e é assumida como padrão da linguagem.

da completa e é assumida como padrão da linguagem. 1.2.5.3 Dependências e Refinamentos Além das associações

1.2.5.3 Dependências e Refinamentos

Além das associações e generalizações, existem a inda dois tipos de relacionamentos em UML. O relacionamento de dependência é uma conexão semântica entre dois modelos de elementos, um independente e outro dependente. Uma mudança no elemento independente irá afetar o modelo dependente.

Como no caso anterior com generalizações, os modelos de elementos podem ser uma classe, um pacote, um caso de uso e assim por diante. Quando uma classe recebe um objeto de outra classe como parâmetro, uma classe acessa o objeto global da outra.

Nesse caso existe uma dependência entre estas duas classes, apesar de não ser explícita. Uma relação de dependência é simbolizada por uma linha tracejada com uma seta no final de um dos lados do relacionamento.

Padrões de Projeto de Software

16

E sobre essa linha o tipo de dependência que existe entre as duas classes. As classes “Amigas” provenientes do C++ são um exemplo de um relacionamento de dependência

do C++ são um exemplo de um relacionamento de dependência Os refinamentos são um tipo de

Os refinamentos são um tipo de relacionamento entre duas descrições de uma mesma coisa, mas em níveis de abstração diferentes e podem ser usados para modelar diferentes implementações de uma mesma coisa (uma implementação simples e outra mais complexa, mas também mais eficiente).

simples e outra mais complexa, mas também mais eficiente). Os refinamentos são simbolizados por uma linha

Os refinamentos são simbolizados por uma linha tracejada comum triângulo no final de um dos lados do relacionamento e são usados em modelos de coordenação. Em grandes projetos, todos os modelos que são feitos devem ser coordenados.

Coordenação de modelos pode ser usada para mostrar modelos em diferentes níveis de abstração que se relacionam e mostram também como modelos em diferentes fases de desenvolvimento se relacionam.

1.2.6 Mecanismos Gerais

AUML utiliza alguns mecanismos em seus diagramas para tratar informações adicionais.

Ornamentos: Ornamentos gráficos são anexados aos modelos de elementos em diagramas e adicionam semânticas ao elemento. Um exemplo de um ornamento é o da técnica de separar um tipo de uma instância. Quando um elemento representa um tipo, seu nome é mostrado em negrito. Quando o mesmo elemento representa a instância de um tipo, seu nome é escrito sublinhado e pode significar tanto o nome da instância quanto o nome do tipo.

Outros ornamentos são os de especificação de multiplicidade de relacionamentos, onde a multiplicidade é um número ou um intervalo que indica quantas instâncias de um tipo conectado pode estar envolvido na relação.

Notas: Nem tudo pode ser definido em uma linguagem de modelagem, sem importar o quanto extensa ela seja. Para permitir adicionar informações a um modelo não poderia ser representado de outra forma, UML provê a capacidade de adicionar Notas.

Uma Nota pode ser colocada em qualquer lugar em um diagrama, e pode conter qualquer tipo de informação.

Padrões de Projeto de Software

17

Padrões de Projeto de Software 17 1.3 PADRÕES DE PROJETOS DE SOFTWARE Um dos principais problemas

1.3 PADRÕES DE PROJETOS DE SOFTWARE

Um dos principais problemas do desenvolvimento de software orientado a objetos é projetar software reutilizável. Na maioria das vezes, e isso aconteceu muito na década passada, e ainda acontece hoje, é que há uma tendência, mesmo em projetos com linguagens orientadas a objetos, em começar uma solução do zero ou usar as velhas técnicas estruturadas utilizadas durante muito tempo, assim “é mais fácil”.

No entanto, o que deve ser observado, é que já existem técnicas orientadas a objetos que solucionam problemas recorrentes de software, não é necessário começar do zero ou partir para técnicas usadas em um passado distante.

Essas técnicas são denominadas padrões de projeto (Design Patterns), assim, ao longo do processo evolutivo do desenvolvimento orientado a objetos foram surgindo soluções brilhantes para problemas recorrentes de software, essas soluções foram documentadas e reutilizadas para cada propósito, proporcionando rapidez no desenvolvimento de software, reutilização de modelo de classes e código e padronização das soluções.

Tudo começou na década de 1970 quando Christopher Alexander, um arquiteto austríaco, lançou as ideias iniciais sobre o uso de padrões para o setor das construções. Em 1987, surgiram os primeiros padrões para a área de Ciência da Computação, propostos por Kent Beck e Ward Cunningham, usando a linguagem Smalltalk.

O modelo MVC (Model-View-Controller) foi um dos pioneiros, usado para atender usuários Smaltalk-80, esse modelo previa o Modelo (a aplicação), a Visão (a tela) e o Controlador (reação da interface dos usuários às entradas de dados do mesmo). Nesse modelo, eram apresentados os fundamentos dos padrões de projeto e como esses poderiam evoluir para outras situações recorrentes de produção de software.

Na década de 1990 surgiram os padrões GoF (Gang of Four –“gangue dos quatro”), dos autores Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Foi lançado o livro: Design Patterns: Elements of Reusable Object-Oriented Software. 1 ed. Estados Unidos: Addison-Wesley, 1995. Mais tarde surgem outros padrões como o padrão GRASP (General Responsibility Assignment Software Patterns), mas de um modo geral, os 23 padrões de projetos GoF definidos pela gangue do quatro, são os mais utilizados no mercado e são esses padrões o objetivo do estudo.

Padrões de Projeto de Software

18

1.4 O QUE É UM PADRÃO DE PROJETO?

O Design Patterns são padrões de classes e de relacionamentos entre as mesmas que aparecem de forma frequente em projetos de software. Tais padrões são categorizados para atender a soluções específicas. Sua utilização é uma atividade que simplifica a reutilização de software. Os padrões aparecem em situações especiais dentro de um sistema como “descrições de objetos e classes comunicantes que são customizadas um contexto particular”. Este descreve uma solução comprovada para um problema, são mais abstratos, menores e menos específicos que frameworks que por sua vez é um conjunto de classes que pode ser utilizado para um tipo específico de projeto de software (análise de domínio, biblioteca de reuso).

Os padrões são dispositivos que permitem que os programas compartilhem conhecimento sobre o seu desenho. Quando programamos, encontramos muitos problemas que ocorrem, ocorreram e irão ocorrer novamente. A questão que nos perguntamos agora é como nós vamos solucionar este problema desta vez? Documentar um padrão é uma maneira de poder reusar e possivelmente compartilhar informação que aprendeu sobre a melhor maneira de se resolver um problema de desenho de software. O catálogo de padrões do GoF (Gang Of Four), contém 23 padrões e está, basicamente, dividido em três seções: Criação (Creational), Estrutural (Structural), Comportamental (Behavioral).

Dentre as principais propriedades dos padrões de projetos podemos citar:

1. Capturam o conhecimento e a experiência de especialistas em projeto de software.

2. Especificam abstrações que estão acima do nível de classes ou objetos isolados ou de componentes.

3. Definem um vocabulário comum para a discussão de problemas e soluções de projeto.

4. Facilitam a documentação e manutenção da arquitetura do software.

5. Auxiliam o projeto de uma arquitetura com determinadas propriedades.

6. Auxiliam o projeto de arquiteturas mais complexas.

Dentre os principais benefícios para a utilização dos padrões de projeto estão:

1. Fornecem soluções que já foram testadas e aprovadas.

2. Tornam o sistema mais fácil de entender e manter.

3. Facilitam o desenvolvimento de módulos coesos.

4. A comunicação entre os participantes do projeto fica mais eficiente

Quando e como utilizar padrões de projetos. A primeira coisa que devemos ter é bom senso. Teste e implemente sua solução e veja se ela funciona. A seguir verifique se ela pode ser otimizada, se for o caso utilize o padrão de projeto que se ajuste ao seu caso para melhorar as deficiências verificadas no seu projeto. Naturalmente isto será mais fácil se tiver uma visão global do seu projeto e seu funcionamento.

Padrões de Projeto de Software

19

Um padrão de projeto pode ser definido como:

É uma técnica usada para identificar, abstrair, documentar e padronizar os diversos aspectos recorrentes do projeto de software orientado a objetos, de modo a torná-los reutilizáveis em outros projetos” (FREEMAN e FREEMAN, 2007).

os padrões de projeto fornecem um vocabulário compartilhado com outros desenvolvedores. Quando você tem um vocabulário, pode se comunicar mais facilmente com outros desenvolvedores e inspirar aqueles que não conhecem os padrões a começar a aprendê-los. Isso também eleva o seu raciocínio sobre as arquiteturas, permitindo que você pense no nível do padrão e não no nível de código” (FREEMAN e FREEMAN, 2007).

Cada padrão descreve um problema no nosso ambiente e o cerne da sua solução, de tal forma que você possa usar essa solução mais de um milhão de vezes, sem nunca fazê-lo da mesma maneira” (GAMMA, HELM, et al., 2000).

Em geral, um padrão tem quatro elementos essenciais:

1. Nome do Padrão: Uma referência usada para descrever um problema recorrente de projeto, suas soluções e consequências em uma ou duas palavras.

2. Problema: Usado para descrever em que situação o padrão deve ser usado. Explica o problema e o contexto.

3. Solução: Usado para descrever os elementos que compõem o padrão:

relacionamentos, responsabilidades e colaborações.

4. Consequências: Vantagens e desvantagens da aplicação do padrão.

1.5 COMO DESCREVER UM PADRÃO DE PROJETO

Existem diversas formas de descrever um padrão de projeto. No entanto, independente do formato, alguns componentes devem ser facilmente reconhecidos quando da leitura de um padrão de projeto. A forma aqui descrita é conhecida como forma canônica.

Nome e Classificação. Deve expressar a essência do padrão. Um bom nome é vital, pois vai se tornar parte do vocabulário do projeto.

Intenção e Objetivo. É uma curta declaração que responde às seguintes questões: o que faz o padrão de projeto? Quais os seus princípios e sua intenção? Que tópico ou problema particular de projeto ele trata?

Propósito. O que faz um padrão de projeto? Qual o problema que se propõe a atacar? Quais os objetivos que deseja alcançar?

Motivação. Descreve o cenário no qual se aplica todas as forças que estão presentes, as classes e os objetos relacionados.

Aplicabilidade. Quais são as situações na qual o padrão de projeto pode ser aplicado? Como reconhecer estas situações?

Estrutura. Um diagrama da UML que represente o padrão.

Padrões de Projeto de Software

20

Participantes. Descreve as classes e/ou objetos que participam no padrão de projeto e suas responsabilidades.

Colaborações. Descreve como os participantes colaboram entre si para realizar suas responsabilidades. Assim, a solução descreve não somente a estruturas estáticas, mas também a dinâmica comportamental dos objetos e classes.

Consequências. Quais os resultados obtidos com a aplicação do padrão de projeto? O que foi resolvido, o que não foi resolvido e que padrão de projeto pode ser aplicado neste momento?

Implementação. Quais são as dicas, os macetes e as técnicas que devem estar claras quando da implementação do padrão. Há questões relativas a uma linguagem específica.

Exemplos de código. Exemplos de sistemas reais, onde o padrão de projeto foi aplicado e que transformações ocorreram dentro de cada contexto.

Padrões relacionados. Qual padrão de projeto tem relação com este padrão de projeto? Quais são as diferenças importantes? Com que outros padrões, este deve ser usado.

1.6 PRINCIPAIS PADRÕES DE PROJETO

1.6.1 Finalidade dos 23 padrões:

1. Adapter. Converter a interface de uma classe em outra interface esperada pelos clientes.

2. Façade. Oferecer uma interface única de nível mais elevado para um conjunto de interfaces de um subsistema.

3. Composite. Permitir o tratamento de objetos individuais e composições desses objetos de maneira uniforme.

4. Bridge. Desacoplar uma abstração de sua implementação para que os dois possam variar independentemente.

5. Singleton. Garantir que uma classe só tenha uma única instância, e prover um ponto de acesso global a ela.

6. Observer. Definir uma dependência um-para-muitos entre objetos para que quando um objeto mudar de estado, os seus dependentes sejam notificados e atualizados automaticamente.

7. Mediator. Definir um objeto que encapsula a forma como um conjunto de objetos interage.

8. Proxy. Prover um substituto ou ponto através do qual um objeto possa controlar o acesso a outro.

9. Chain of Responsibility. Compor objetos em cascata para, através dela, delegar uma requisição até que um objeto a sirva.

10. Flyweight. Usar compartilhamento para suportar eficientemente grandes

quantidades de objetos complexos.

11. Builder. Separar a construção de objeto complexo da representação para criar

representações diferentes com mesmo processo.

Padrões de Projeto de Software

21

12.

Factory Method. Definir uma interface para criar um objeto, mas deixar que subclasses decidam que classe instanciar.

13.

Abstract Factory. Prover interface para criar famílias de objetos relacionados ou dependentes sem especificar suas classes concretas.

14.

Prototype. Especificar tipos a criar usando uma instância como protótipo e criar novos objetos ao copiar este protótipo.

15.

Memento. Externalizar o estado interno de um objeto para que o objeto possa ter esse estado restaurado posteriormente.

16.

Template Method. Definir o esqueleto de um algoritmo dentro de uma operação, deixando alguns passos a serem preenchidos pelas subclasses.

17.

State. Permitir a um objeto alterar o seu comportamento quanto o seu estado interno mudar.

18.

Strategy. Definir uma família de algoritmos, encapsular cada um, e fazê-los intercambiáveis.

19.

Command. Encapsular requisição como objeto, para clientes parametrizarem diferentes requisições, filas, e suportar operações reversíveis.

20.

Interpreter. Dada uma linguagem, definir uma representação para sua gramática junto com um interpretador.

21.

Decorator. Anexar responsabilidades adicionais a um objeto dinamicamente.

22.

Iterator. Prover uma maneira de acessar elementos de um objeto agregado sequencialmente sem expor sua representação interna.

23.

Visitor. Representar uma operação a ser realizada sobre os elementos de uma estrutura de objetos.

Os

23 padrões de projeto se dividem em categorias segundo (GAMMA, HELM, et

al., 2000), e são elas: Criação de classes de objetos, alteração da Estrutura de um programa, e controle de seu Comportamento. A figura 1 mostra os padrões GoF, divididos em suas respectivas categorias.

O primeiro critério, chamado de finalidade, reflete o que um padrão faz. Os

padrões podem ter finalidade de criação, estrutural ou comportamental. Os padrões de

criação se preocupam com o processo de criação de objetos. Os padrões de criação se preocupam com o processo de criação de objetos. Os padrões estruturais lidam com a composição de classes ou de objetos. Os padrões comportamentais caracterizam as maneiras pelas quais classes ou objetos interagem e distribuem responsabilidades.

O segundo critério, chamado de escopo, especifica se o padrão se aplica principalmente a classes ou objetos. Os padrões para classes lidam com os relacionamentos entre classes e suas subclasses. Esses relacionamentos são estabelecidos através do mecanismo de herança, assim eles são estáticos – fixados em tempo de compilação. Os padrões para objetos lidam com relacionamentos entre objetos que podem ser mudados em tempos de execução e são mais dinâmicos. Quase todos utilizam a herança em certa medida.

Padrões de Projeto de Software

22

Padrões de Projeto de Software 22 Figura 1 : Classificação dos 23 padrões segundo GoF O

Figura 1: Classificação dos 23 padrões segundo GoF

O primeiro critério, chamado de finalidade, reflete o que um padrão faz. Os padrões podem ter finalidade de criação, estrutural ou comportamental. Os padrões de criação se preocupam com o processo de criação de objetos. Os padrões de criação se preocupam com o processo de criação de objetos. Os padrões estruturais lidam com a composição de classes ou de objetos. Os padrões comportamentais caracterizam as maneiras pelas quais classes ou objetos interagem e distribuem responsabilidades.

O segundo critério, chamado de escopo, especifica se o padrão se aplica principalmente a classes ou objetos. Os padrões para classes lidam com os relacionamentos entre classes e suas subclasses. Esses relacionamentos são estabelecidos através do mecanismo de herança, assim eles são estáticos – fixados em tempo de compilação. Os padrões para objetos lidam com relacionamentos entre objetos que podem ser mudados em tempos de execução e são mais dinâmicos. Quase todos utilizam a herança em certa medida.

Os padrões de criação voltados para classes deixam alguma parte da criação de objetos para subclasses, enquanto que os padrões de criação voltados para objetos postergam esse processo para outro objeto. Os padrões estruturais voltados para classes utilizam a herança para compor classes, enquanto que os padrões estruturais voltados para objetos descrevem maneiras de montar objetos. Os padrões comportamentais voltados para classes usam a herança para descrever algoritmos e fluxo de controle, enquanto que os voltados para objetos descrevem como um grupo de objetos coopera para executar uma tarefa que um único objeto não pode executar sozinho (GAMMA, HELM, et al., 2000).

Para Metsker (METSKER, 2002) os 23 padrões são classificados em cinco grupos como mostrado na figura 2, por intenção (problema a ser solucionado):

1. Oferecer uma interface,

Padrões de Projeto de Software

23

2. Atribuir uma responsabilidade,

3. Realizar a construção de classes ou objetos

4. Controlar formas de operação

5. Implementar uma extensão para a aplicação

operação 5. Implementar uma extensão para a aplicação Figura 2 : Classificação dos padrões GoF segundo

Figura 2: Classificação dos padrões GoF segundo Metsker

1.6.2 Como selecionar um padrão?

1. Considere como os padrões solucionam os problemas de projeto.

2. Analise seu problema e compare com o objetivo de cada padrão.

3. Veja como os padrões envolvidos se relacionam entre si.

4. Estude padrões de propósito ou intenção similar (veja formas de classificação).

5. Examine causas comuns que podem forçar o redesign do seu sistema.

6. Considere o que deve variar no seu design.

Padrões de Projeto de Software

24

2. PADRÕES GoF

2.1 PADRÕES DE CRIAÇÃO

Os padrões de criação abstraem o processo de instanciação. Eles ajudam a tornar um sistema independente de como seus objetivos são criados, compostos e representados.

Um padrão de criação de classe usa a herança para variar a classe que é instanciada, enquanto que um padrão de criação de objeto delegará a instanciação para outro objeto.

2.1.1 Padrão Abstract Factory

Propósito:

Fornecer uma interface para criação de famílias de objetos relacionados ou dependentes sem especificar suas classes concretas.

Motivação

sem especificar suas classes concretas. Motivação  Considere uma aplicação com interface gráfica que é

Considere uma aplicação com interface gráfica que é implementada para plataformas diferentes (Motif para UNIX e outros ambientes para Windows e MacOS).

As classes implementando os elementos gráficos não podem ser definidas estaticamente no código. Precisamos de uma implementação diferente para cada ambiente. Até em um mesmo ambiente, gostaríamos de dar a opção ao usuário de implementar diferentes aparências (look-and-feels).

Podemos solucionar este problema definindo uma classe abstrata para cada elemento gráfico e utilizando diferentes implementações para cada aparência ou para cada ambiente.

Ao invés de criarmos as classes concretas com o operador new, utilizamos uma Fábrica Abstrata para criar os objetos em tempo de execução.

O código cliente não sabe qual classe concreta utilizamos.

Padrões de Projeto de Software

25

Aplicabilidade

Use o padrão Abstract Factory quando:

1. O sistema precisa ser independente de como os produtos são criados, compostos e representados.

2. O sistema deve ser configurado com uma de múltiplas famílias de produtos.

3. Produtos de uma família devem ser sempre utilizados em conjunto e isto precisa ser garantido.

4. Deseja-se disponibilizar uma biblioteca de classes de produtos, mas revelar somente as suas interfaces, não suas implementações.

Estrutura

as suas interfaces, não suas implementações. Estrutura Participantes  AbstractFactory : o declara uma

Participantes

AbstractFactory:

o declara uma interface para operações que criam objetos de produto abstratos.

ConcreteFactory:

o implementa as operações para criar objetos produto concretos.

AbstractProduct:

o declara uma interface para um tipo de objeto produto.

ConcreteProduct:

o define o objeto produto a ser criado pela fábrica concreta correspondente e implementa a interface AbstractProduct.

Client:

o usa somente interfaces declaradas pelas classes AbstractFactory e

AbstractProduct.

Padrões de Projeto de Software

26

Colaborações

Normalmente uma única instância de fábrica concreta é criada em runtime. Esta fábrica cria produtos com uma implementação em particular. Para criar outros produtos deve-se utilizar uma fábrica diferente.

As classes abstratas transferem a responsabilidade de criação dos objetos produto para as suas subclasses.

Consequências

Isola as classes concretas: Ele controla as classes dos objetos que a aplicação cria. Clientes manipulam instâncias somente através de suas interfaces abstratas. Nomes de classes estão isolados nas fábricas concretas, eles não aparecem no código do cliente.

Torna fácil a troca de famílias de produtos: A classe da fábrica concreta sendo utilizada aparece somente uma vez na aplicação, facilitando modificações. A família de produtos mudaria toda de uma vez.

Promove consistência entre produtos: Garante que os objetos utilizados são todos de uma mesma família, representada pela fábrica concreta sendo utilizada.

Dificulta a inserção de novos tipos de produtos: A interface da fábrica abstrata torna fixo o conjunto de produtos que podem ser criados. Suportar um novo produto exige a extensão da interface da fábrica abstrata e a modificação de todas as suas subclasses.

Implementação. A seguir algumas técnicas úteis para implementar o padrão Abstract factory.

Fábricas como Singletons: Geralmente precisa-se de somente uma instância da fábrica concreta por aplicação.

Criando os produtos: A fábrica abstrata somente define uma interface para criação de produtos, geralmente através de um Factory Method para cada produto. As fábricas concretas implementam esses Factory Methods para instanciar os objetos. A implementação é simples, mas requer uma fábrica concreta para cada família de produtos, mesmo que elas sejam ligeiramente diferentes. Se estão previstas muitas famílias, a fábrica concreta pode ser implementada usando o padrão Prototype. Teríamos apenas uma fábrica concreta, inicializada com os protótipos dos produtos desejados. Outra variação é utilizar meta-classes, se disponíveis na linguagem.

Definindo fábricas extensíveis: Na fábrica abstrata, os tipos de produtos fazem parte das assinaturas dos Factory Methods. Adicionar um novo produto requer mudar a interface da fábrica abstrata e de todas as classes dela dependentes. Uma solução mais flexível porém menos segura é adicionar um parâmetro ao Factory Methodindicando o tipo de produto a ser criado. A fábrica abstrata (e concreta) teria somente um Factory Method.

Padrões de Projeto de Software

27

Entretanto, visto que os produtos deverão ter a mesma classe-base, o cliente os enxergará de uma única maneira, sem distinguir os tipos dos produtos.

Exemplo prático 1. Neste exemplo, a classe abstrata WidgetFactory possui duas especializações: MotifWidgetFactory para widgets Motif e QtWidgetFactory para widgets Qt. Essas especializações são classes concretas capazes de “produzir” os elementos da interface gráfica. O cliente do toolkit obtém os elementos gráficos de que necessita através da classe (interface) WidgetFactory sem ter conhecimento das classes concretas. Da mesma maneira, o cliente somente interage com as interfaces que representam os elementos produzidos pela classe Abstract Factory (no exemplo, a classe Janela e a classe Botao).

Factory (no exemplo, a classe Janela e a classe Botao ). Figura 2: Estrutura exemplo prático

Figura 2: Estrutura exemplo prático 1.

Implementação:

public abstract class E2WidgetFactory { public static E2WidgetFactory obterFactory(){ if( E2Configuracao.obterInterfaceGraficaAtual() == E2Configuracao.E2MotifWidget ){ return new E2MotifWidgetFactory();

 

}

else{ return new E2QtWidgetFactory(); }

}

public abstract E2Botao criarBotao();

}

 

E2WidgetFactory.java

public class E2QtWidgetFactory extends E2WidgetFactory{ @Override public E2Botao criarBotao() { return new E2BotaoQt();

}

}

 

E2QtWidgetFactory.java

public class E2MotifWidgetFactory extends E2WidgetFactory{ @Override public E2Botao criarBotao() { return new E2BotaoMotif();

}

}

Padrões de Projeto de Software

28

E2MotifWidgetFactory.java

public abstract class E2Botao { public abstract void desenhar();

}

E2Botao .java

public class E2BotaoMotif extends E2Botao{ @Override public void desenhar() { System.out.println(">> Eu sou um botao Motif! <<");

}

}

E2BotaoMotif.java

public class E2BotaoQt extends E2Botao{ @Override public void desenhar() { System.out.println(">> Eu sou um botao Qt! <<");

}

}

E2BotaoQt.java

public class E2Configuracao { public static final E2Configuracao E2MotifWidget = new E2Configuracao(); public static Object obterInterfaceGraficaAtual() { return E2MotifWidget;

}

}

E2Configuracao.java

public class E2Cliente { public static void main(String[] args){ E2WidgetFactory factory = E2WidgetFactory.obterFactory(); E2Botao botao = factory.criarBotao(); botao.desenhar();

}

}

E2Cliente.java

Exemplo prático 1a. Estabelecimentos comerciais normalmente oferecem aos clientes diversas opções de pagamento. Por exemplo, clientes podem efetuar pagamentos com dinheiro, cheque, cartões de crédito ou débito, entre outros.

Pagamentos com cartões são realizados por meio de uma máquina de cartão, oferecida e instalada por empresas como Cielo e Redecard. Geralmente, essa máquina é capaz de lidar com cartões de diferentes bandeiras (como Visa e Mastercard).

Nosso objetivo é desenvolver uma aplicação capaz de se comunicar com as diferentes bandeiras e registrar pagamentos. No momento do pagamento, a máquina de cartão deve enviar as informações relativas à transação (como valor e senha) para a bandeira correspondente ao cartão utilizado. Além disso, a máquina deve aguardar uma resposta de confirmação ou recusa do pagamento.

Exercícios 1:

1. Implemente a classe Janela da figura 2.

2. Implemente uma aplicação que constrói Figuras: (Use uma abstract Factory para controlar a criação de todos os objetos)

Pontos (x, y)

Circulos (Ponto, raio)

Retangulos (Ponto, Ponto)

Padrões de Projeto de Software

29

Triangulos (Ponto, Ponto, Ponto)

2.1.2 Padrão Builder

Propósito

Separar a construção de um objeto complexo de sua representação para que o mesmo processo de construção possa criar representações diferentes (GAMMA, HELM, et al., 2000). Mover a lógica de construção de uma classe para um objeto externo, a fim de reduzir a complexidade da mesma e permitir a construção gradual de objetos-alvo a partir dessa classe (METSKER, 2002).

Motivação

Nem sempre é fácil coletar os requisitos que movem a criação de uma classe. Mais trabalhoso é, porém, instanciar objetos de uma classe que variem de acordo com os requisitos apresentados. Por exemplo, mudar o layout de uma tela, em tempo de execução, quando o usuário seleciona uma opção condizente.

Mas, se fosse possível separar da classe os seus métodos – representam o comportamento e a construção dessa classe – numa estrutura de dados a parte, seria possível também, utilizar essa estrutura para coletar as opções de um cliente e construir objetos diferentes, conhecendo apenas os parâmetros necessários para esta construção, provenientes dos atributos que compõem a classe em questão.

Desta maneira se apresenta o padrão Builder, um construtor de objetos, baseado nos métodos de criação e comportamento de uma classe, que pode instanciar objetos diferentes, conhecendo apenas os atributos que compõem a classe em questão.

Aplicabilidade

Builder permite que uma classe se preocupe com apenas uma parte da construção de um objeto. É útil em algoritmos de construção complexos.

Use-o quando o algoritmo para criar um objeto complexo precisar ser independente das partes que compõem o objeto e da forma como o objeto é construído.

Builder também suporta substituição dos construtores, permitindo que a mesma interface seja usada para construir representações diferentes dos mesmos dados.

Use quando o processo de construção precisar suportar representações diferentes do objeto que está sendo construído.

Padrões de Projeto de Software

30

Estrutura

Padrões de Projeto de Software 30 Estrutura Figura 3: Estrutura do Builder Participantes  Builder o

Figura 3: Estrutura do Builder

Participantes

Builder

o Especifica uma interface abstrata para criação de partes de um objeto- produto.

ConcreteBuilder

o Constrói e monta partes do produto pela implementação da interface de Builder; define e mantém a representação que cria; fornece uma interface para recuperação do produto.

Director.

o Constrói um objeto usando a interface de Builder.

Product

o Representa o objeto complexo em construção. ConcreteBuilder constrói a representação interna do produto e define o processo pelo qual ele é montado; inclui classes que definem as partes constituintes, inclusive as interfaces para a montagem das partes no resultado final.

Consequências

Vantagens segundo (METSKER, 2002):

Redução da extensão e da complexidade de uma classe;

Independência entre a representação de um objeto e a sua construção;

Criação de regras graduais de construção para um objeto;

Geração de construções diversificadas de um tipo de objeto, ou de construções de objetos diversificados entre si, a partir de um construtor-base;

Desvantagem:

1. Por ser bastante flexível, este padrão só apresentará desvantagem se o seu construtor-base for mal planejado, o que poderia resultar em construções redundantes ou com baixo aproveitamento operacional.

Padrões de Projeto de Software

31

Exemplo prático 2

Padrões de Projeto de Software 31 Exemplo prático 2 Figura 4: UML exemplo 2 Implementação :

Figura 4: UML exemplo 2

Implementação:

/** "Product" */ class Pizza { private String dough = ""; private String sauce = ""; private String topping = "";

}

public void setDough(String dough)

public void setSauce(String sauce) { this.sauce = sauce; } public void setTopping(String topping) { this.topping = topping; }

{ this.dough = dough; }

/** "Abstract Builder" */ abstract class PizzaBuilder { protected Pizza pizza;

public Pizza getPizza() { return pizza; } public void createNewPizzaProduct() { pizza = new Pizza(); }

public abstract void buildDough(); public abstract void buildSauce(); public abstract void buildTopping();

}

/** "ConcreteBuilder" */ class HawaiianPizzaBuilder extends PizzaBuilder {

}

public void buildDough() public void buildSauce()

public void buildTopping() { pizza.setTopping("ham+pineapple"); }

{ pizza.setDough("cross"); } { pizza.setSauce("mild"); }

/** "ConcreteBuilder" */ class SpicyPizzaBuilder extends PizzaBuilder {

}

public void buildDough() public void buildSauce()

public void buildTopping() { pizza.setTopping("pepperoni+salami"); }

{ pizza.setDough("pan baked"); } { pizza.setSauce("hot"); }

/** "Director" */

Padrões de Projeto de Software

32

class Waiter { private PizzaBuilder pizzaBuilder;

public void setPizzaBuilder(PizzaBuilder pb) { pizzaBuilder = pb; } public Pizza getPizza() { return pizzaBuilder.getPizza(); }

public void constructPizza() { pizzaBuilder.createNewPizzaProduct(); pizzaBuilder.buildDough(); pizzaBuilder.buildSauce(); pizzaBuilder.buildTopping();

}

}

/** A customer ordering a pizza. */ class BuilderExample { public static void main(String[] args) { Waiter waiter = new Waiter(); PizzaBuilder hawaiian_pizzabuilder = new HawaiianPizzaBuilder(); PizzaBuilder spicy_pizzabuilder = new SpicyPizzaBuilder();

waiter.setPizzaBuilder( hawaiian_pizzabuilder ); waiter.constructPizza();

Pizza pizza = waiter.getPizza(); System.out.println(" Pizza tipo "+pizza.getDough());

}

}

Exercícios 2:

1. Adicionar outros tipos de pizzas ao código anterior.

2. Uma aplicação precisa construir objetos Pessoa, e Empresa. Para isto, precisa ler dados de um banco para cada produto.

1. Para construir uma Pessoa é preciso obter nome e identidade. Apenas se os dois forem lidos a pessoa pode ser criada.

2. Para construir uma empresa é preciso ler o nome e identidade do responsável

e depois construir a pessoa do responsável.

3. Mostre como poderia ser implementada uma aplicação que realizasse as tarefas

acima. Simule os dados com Strings.

2.1.3 Padrão Factory Method

Propósito

Definir uma interface ou classe abstrata para a criação de um objeto, permitindo decidir qual das implementações ou subclasses devem ser instanciadas; deixa uma classe deferir instanciação para subclasses (GAMMA, HELM, et al., 2000). Retornar uma instância dentre muitas possíveis classes, dependendo dos dados providos a ele.

Motivação

Útil para se construir objetos individuais, para um propósito específico, sem que a construção requeira conhecimento das classes específicas sendo instanciadas. Uma classe de abstração é criada, contendo um método em que decide qual das opções de classe retornar. Então, apenas este método é invocado, sem conhecer a classe que será retornada por ele. Por isso, este padrão é chamado de Factory Method (traduzindo:

Padrões de Projeto de Software

33

“método-fábrica”): um método é responsável pela “fabricação” de um objeto (GAMMA, HELM, et al., 2000).

Aplicabilidade

Sugestões para o uso desse padrão (GAMMA, HELM, et al., 2000):

2. Quando uma classe não pode antecipar ou conhecer a classe dos objetos que deve criar;

3. Quando uma classe quer suas subclasses para especificar os objetos que cria;

4. Quando classes delegam responsabilidade à alguma das várias subclasses ajudantes, e deseja-se localizar qual é a subclasse ajudante acessada.

Estrutura

localizar qual é a subclasse ajudante acessada. Estrutura Figura 5: Estrutura do padrão Factory Method Participantes

Figura 5: Estrutura do padrão Factory Method

Participantes

Creator. Declara o factory method (método de fabricação) que retorna o objeto da classe Product (produto). Este elemento também pode definir uma implementação básica que retorna um objeto de uma classe ConcreteProduct (produto concreto) básica;

ConcreteCreator. Sobrescreve o factory method e retorna um objeto da classe ConcreteProduct;

Product. Define uma interface para os objectos criados pelo factory method;

ConcreteProduct. Uma implementação para a interface Product.

Consequências

Vantagens:

Elimina a necessidade de acoplar classes específicas para aplicação em nível de código (GAMMA, HELM, et al., 2000) – na programação, se lida apenas com a superclasse ou interface, para conhecer os métodos e

Padrões de Projeto de Software

34

atributos de uma forma genérica, o que permite manipular as subclasses ou implementações de maneira particular.

Dá maior flexibilidade para as classes – criar objetos numa classe que possui o “método-fábrica” é sempre mais flexível que fazê-lo em separado, assim o “método-fábrica” funciona como uma conexão para que uma das subclasses possa prover uma versão estendida de um objeto.

Desvantagem:

Em alguns casos, pode ser criada uma relação superclasse-subclasse para a criação de classes que suportam o “método-fábrica”, e de acordo com a classe a ser criada, uma das várias subclasses podem sobrescrever o “método-fábrica”, a fim de conectar hierarquias de classes paralelas e fornecer maior portabilidade (GAMMA, HELM, et al., 2000). Entretanto, esta é uma solução viável se, e somente se, a especialização da classe do método fábrica não for estritamente necessária (como no caso de um número elevado de implementações diferentes para o “método-fábrica”). Caso contrário, a relação custo-planejamento- implementação de se especializar uma classe apenas para instanciar um objeto de uma subclasse de outra superclasse pode se revelar bastante improdutiva, podendo-se optar por uma alternativa mais simples como a criação classes para o “método-fábrica” em separado

Exercício prático 3

o “método-fábrica” em separado Exercício prático 3 Figura 6: UML do Exemplo 3 Implementação /** *

Figura 6: UML do Exemplo 3

Implementação

/**

* Abstração de uma Aplicação capaz de manipular

* documentos. */ abstract class Aplicacao {

Padrões de Projeto de Software

35

private Documento doc;

/** * Abstração do Factory Method */ abstract Documento criaDocumento();

void novoDocumento() { this.doc = this.criaDocumento();

}

void abrirDocumento() { this.doc.abrir();

}

}

/**

* Abstração de um Documento.

*/ abstract class Documento {

void abrir() { System.out.println("Documento:Abrir documento!");

}

void fechar() { System.out.println("Documento:Fechar documento!");

}

void gravar() { System.out.println("Documento:Gravar documento!");

}

}

/**

* Esta classe concreta contém a implementação

* de uma aplicação capaz de manipular documentos

* do tipo MeuDocumento. */ class MinhaAplicacao extends Aplicacao {

/**

* Uma implementação do Factory Method. Este método é

* especializado na criação de documentos do tipo MeuDocumento */ Documento criaDocumento() { return new MeuDocumento();

}

}

/**

* Esta classe concreta contém a implementação

* de um tipo de documento específico. */ class MeuDocumento extends Documento { private String word ="Word"; private String excel="Excel";

}

/* testde Factory Method */ public class FactoyMethodTest { public static void main(String[] args){ MinhaAplicacao aplicacao = new MinhaAplicacao(); Documento docu = aplicacao.criaDocumento(); docu.abrir(); // MeuDocumento documento = new MeuDocumento(); System.out.println(" Documento: "+documento.getWord());

}

}

Padrões de Projeto de Software

36

Exercícios 3.

1. Implemente a aplicação mostrada na figura 7 abaixo usando Factory Method para criar os objetos.

2. Crie um objeto construtor para cada tipo de objeto (XXXFactory para criar figuras do tipo XXX)

3. Utilize a fachada Figura, onde os construtores são guardados, e um método estático que seleciona o construtor desejado com uma chave.

estático que seleciona o construtor desejado com uma chave. Figura 7: Exercício a ser implementado 2.1.4

Figura 7: Exercício a ser implementado

2.1.4 Padrão Prototype

Propósito

Especificar os tipos de objetos a serem criados, usando uma instância prototípica, criando novos objetos através da cópia desse protótipo. Substituir a geração de instâncias não-inicializadas de uma classe, fornecendo novos objetos a partir de uma classe-exemplo.

Motivação

A criação de classes se faz mais simples quando é possível partir de um exemplo, para então criar as especializações condizentes com as necessidades do domínio da aplicação. Uma alternativa é criar classes especializadas que herdem, ou seja, que sejam subclasses, de uma classe com atributos e métodos mais genéricos (ou até mesmo uma classe abstrata) com parâmetros de visibilidade acessíveis, alterando-os apenas quando for preciso na criação da classe especializada.

Entretanto, essa alternativa apresenta um problema de ordem prática: a geração excessiva de classes especializadas (ou subclasses), para cada necessidade específica, pode “inchar” o sistema, fazendo com que essas classes sempre sejam carregadas, mesmo que o seu uso seja reduzido. Ainda, tratando-se de visibilidade, para a criação de subclasses, estas devem conhecer a superclasse que lhes fornecerá as características comuns a elas, para que, então, sejam diferenciadas, segundo as necessidades de cada objeto.

Para sistemas remotos, nem sempre isso é possível: na maioria das situações, um servidor de classes fica isolado dos clientes, e estes não podem conhecer a estrutura da

Padrões de Projeto de Software

37

superclasse; apenas podem saber dos parâmetros – a serem informados em chamadas de métodos – que participam da criação de produtos (que no caso, seriam as subclasses).

Nesse contexto, Prototype se dispõe a simplificar a criação de novas classes a partir de uma classe-exemplo, copiando-a fielmente, para que, nessa cópia, sejam feitas as especializações intrínsecas a cada dependência da aplicação. Além de reduzir sensivelmente o número de classes o uso desse padrão isola o usuário da estrutura do objeto, ou a classe-exemplo de seus produtos – o que ainda facilita a criação de novas classes-exemplo, quando for estritamente necessário.

Aplicabilidade

O padrão Prototype pode ser utilizado em sistemas que precisam ser independentes da forma como os seus componentes são criados, compostos e representados. O padrão Prototype pode ser útil em sistemas com as seguintes características:

Sistemas que utilizam classes definidas em tempo de execução;

Sistemas que utilizam o padrão Abstract Factory para criação de objetos. Neste caso, a hierarquia de classes pode se tornar muito complexa e o padrão Prototype pode ser uma alternativa mais simples, por realizar a mesma tarefa com um número reduzido de classes;

Sistemas que possuem componentes cujo estado inicial possui poucas variações e onde é conveniente disponibilizar um conjunto pré-estabelecido de protótipos que dão origem aos objetos que compõem o sistema.

Quando as classes para instanciação são especificadas em tempo de execução ou carregadas dinamicamente;

Para evitar a construção de uma hierarquia de classes-exemplo estritamente especializada ou demasiadamente ligada a um tipo de produto;

Quando as instâncias de uma classe podem ter apenas uma ou poucas maneiras de manifestar seu estado.

Estrutura

uma ou poucas maneiras de manifestar seu estado. Estrutura Figura 8: Estrutura UML do padrão Prototype

Figura 8: Estrutura UML do padrão Prototype

Padrões de Projeto de Software

38

Participantes

Prototype. Uma classe que declara uma interface para objetos capazes de clonar a si mesmo;

PrototypeConcreto. Implementação uma operação para clonar a si próprio.

Cliente. Cria um novo objeto através de um protótipo que é capaz de clonar a si mesmo.

Consequências

Vantagens:

Isolamento entre os produtos (subclasses ou implementações) e suas classes- exemplo; esta vantagem se dá por dois fatores:

o Facilita a criação de novas classes-exemplo – quando for devidamente necessário – menos acopladas, o que é apropriado para classes que diferem apenas em seus atributos, mas não em seus comportamentos; o Fornece de forma transparente e independente acesso ao sistema, apenas pelo conhecimento dos parâmetros necessários para a criação das cópias, a partir das classes exemplo.

Redução do número de subclasses: as cópias podem suprir as especializações – sejam elas por herança ou por implementação de interfaces – pois uma vez criadas com um formato padrão, podem ser modificadas dentro de cada contexto e necessidade inerentes à aplicação.

Configuração para o carregamento dinâmico de novas classes: com este padrão, é possível configurar a instanciação de classes que originalmente não estavam ligadas ao programa, pois ao invés de invocar um construtor estático, dá suporte à criação de instâncias em tempo de execução, assim que sua respectiva classe é carregada.

Desvantagem:

A única desvantagem encontrada foi a da utilização do método Clone:

o

Este método cria uma cópia exata de um objeto que lhe for passado como parâmetro. Uma cópia exata carrega consigo uma espécie de “fotografia” do objeto: não apenas seu comportamento é “clonado”, mas também seu estado, ou seja, um novo objeto com os mesmos campos do objeto original;

o

Entretanto, quando se quer copiar um objeto, quase sempre se deseja um “novo objeto” e não um “objeto novo”, o que significa uma cópia não instanciada daquele objeto, ou com os campos ajustados para valores iniciais, que sejam default para toda a aplicação.

Assim, o programador deve ter em mente esta diferença ao implementar este padrão, pois objetos clonados podem apresentar problemas de inclusão de

Padrões de Projeto de Software

39

classes não existentes e/ou referencias circulares (dependências de parâmetros ou resultados que sejam internos aos objetos e que não sejam levados em consideração na clonagem).

Exercício pratico 4

levados em consideração na clonagem). Exercício pratico 4 Figura 8: Exemplo do padrão Prototype Implementação /**

Figura 8: Exemplo do padrão Prototype

Implementação

/**

* Prototype class

*/ abstract class Prototype implements Cloneable { @Override public Prototype clone() throws CloneNotSupportedException { return (Prototype)super.clone();

}

public abstract void setX(int x);

public abstract void printX();

public abstract int getX();

}

/**

* Implementation of prototype class

*/ class PrototypeImpl extends Prototype {

int x;

public PrototypeImpl(int x) { this.x = x;

}

public void setX(int x) {

this.x = x;

}

public void printX() { System.out.println("Valor :" + x);

}

public int getX() { return x;

}

}

/**

Padrões de Projeto de Software

40

* Client code */ public class PrototypeTest { public static void main(String args[]) throws CloneNotSupportedException { Prototype prototype = new PrototypeImpl(1000);

for (int i = 1; i < 10; i++) { Prototype tempotype = prototype.clone();

// Usage of values in prototype to derive a new value. tempotype.setX( tempotype.getX() * i); tempotype.printX();

}

}

}

Exercício 4.

1. Elabore um programa para exercitar a criação do padrão Prototype:

a. O Cliente é um revendedor de automóveis VW.

b. O (Abstract) Prototype é um modelo de automóvel GOL.

c. Os ConcretePrototype são modelos de Automóveis GOL.

Modelo Luxo.

i. Gol com Motor AP de 250HP e Pneus 13/185.

Modelo Standard.

i. Gol com Motor AP de 180HP e Pneus 13/185.

d. Crie uma aplicação de venda de automóveis que “vende” Gol através da clonagem dos modelos Luxo e Standard

2.1.5 Padrão Singleton

Propósito

Padrão de projeto simples que ilustra várias características necessárias aos padrões de projeto. Classificado criacional, pois está relacionado com processo de criação de objetos, possuindo inúmeras aplicações e implementação bastante direta.

Tem como propósito garantir a existência de uma instância única de uma classe específica, que possa ser acessada de maneira global e uniforme.

Motivação

O que motiva sua existência é que muitas aplicações necessitam garantir a ocorrência de uma instância de classes específicas, pois tais objetos podem fazer uso de recursos cuja utilização deve ser exclusiva, ou porque se deseja que os demais elementos do sistema compartilhem um único objeto particular.

Estas situações ocorrem quando vários subsistemas utilizam um único arquivo de configuração, permitindo sua modificação concorrente. Ou quando só se deve

Padrões de Projeto de Software

41

estabelecer uma conexão exclusiva com um banco de dados ou um sistema remoto, devendo ser compartilhado por várias tarefas paralelas; entre muitas outras.

Ao mesmo tempo não se quer que os usuários destas classes zelem por esta condição de unicidade, mas se deseja oferecer acesso simples à instância única que deverá existir, portanto se adiciona estas responsabilidades às classes que deverão constituir um Singleton.

Aplicabilidade

Use o padrão Singleton quando:

Deve haver uma única instância de uma classe e esta deve ser acessada a partir de um ponto de acesso bem-conhecido.

A instância única deve ser extensível através de subclasses e clientes podem usar instâncias diferentes polimorficamente, sem modificação de código.

Estrutura

polimorficamente, sem modificação de código. Estrutura Figura 9: Padrão UML do Singleton Participantes 

Figura 9: Padrão UML do Singleton

Participantes

Singleton define uma operação Instance que deixa clientes acessarem sua única instância. Instance é uma operação de classe.

Pode ser responsável para criar sua própria instância única.

Consequências

Vantagens:

Acesso controlado à instância única;

O Singleton tem controle sobre como e quando clientes acessam a instância;

Espaço de nomes reduzido;

Padrões de Projeto de Software

42

O Singleton é melhor que variáveis globais, já que as “globais” podem ser encapsuladas na instância única, deixando um único nome externo visível;

Permite refinamento de operações e de representação;

Várias classes Singleton (relacionadas ou não via herança) podem obedecer a mesma interface, permitindo que um Singleton particular seja escolhido para trabalhar com uma determinada aplicação em tempo de execução;

Mais flexível que métodos estáticos;

Exercício prático 5

flexível que métodos estáticos; Exercício prático 5 Figura 10: Exemplo UML do Singleton Implementação //

Figura 10: Exemplo UML do Singleton

Implementação

// public class Singleton {

private static Singleton instance = = new Singleton();

}

private Singleton() {

}

public static synchronized Singleton if (instance == null) {

}

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

instance = new Singleton();

}

return instance;

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

getInstance() {

Exercício 5

1. Usando o padrão singleton implemente uma conexão a um banco de dados.

Padrões de Projeto de Software

43

2.2 PADRÕES ESTRUTURAIS

Os padrões estruturais se preocupam com a forma como classes e objetos são compostos para formar estruturas maiores. Os padrões estruturais de classes utilizam a herança para compor interfaces ou implementações. Estes padrões são utilizados para definir a composição de objetos e controlar o acesso aos subsistemas de objetos.

Os exemplos a seguir descrevem os prós e contras dos requisitos do sistema e como se aplicam aos diferentes padrões de projeto estrutural. Em oposição aos padrões arquiteturais que definem completamente uma solução ou subsistema, em geral existem vários padrões de projeto estrutural em um único framework.

2.2.1 Padrão Adapter

Propósito

Converter a interface de uma classe em outra interface, esperada pelos clientes.

O Adapter permite que classes com interfaces incompatíveis trabalhem em conjunto. O

padrão Adapter facilita a conversão da interface de uma classe para outra interface mais

interessante para o cliente, fazendo com que várias classes possam trabalhar em conjunto independentemente das interfaces originais.

Motivação

O Adapter é utilizado quando uma classe já existente e sua interface não combinam com a esperada pelo cliente ou se se deseja criar uma classe reutilizável que coopera com classes não relacionadas ou não previstas, ou seja, classes q não necessariamente tenham interfaces compatíveis, entre outras utilizações. O padrão Adapter tem finalidade estrutural e abrange tanto escopo de classe quanto de objeto, por

isso existe o adaptador de classe e o adaptador de objetos, o primeiro é utilizado quando

o alvo é uma interface, consequentemente usa herança múltipla, o segundo é utilizado quando o alvo é uma classe e faz uso da agregação.

quando o alvo é uma classe e faz uso da agregação. Fig. 11: Padrão Adapter ©

Fig. 11: Padrão Adapter

Padrões de Projeto de Software

44

Aplicabilidade

Use o padrão Adapter quando:

Você quiser usar uma classe existente, mas sua interface não corresponde à interface de que necessita.

Você quiser criar uma classe reutilizável que coopera com classes não relacionadas ou não previstas, ou seja, classes que não necessariamente tenham interfaces compatíveis.

(Somente para adaptadores de objetos) você necessitar de usar variáveis subclasses existentes, porém, for impraticável adaptar estas interfaces criando subclasses para cada uma. Um adaptador de objeto pode adaptar a interface da sua classe-mãe.

Estrutura

Um adaptador de classe usa a herança múltipla para adaptar uma interface á

outra.

a herança múltipla para adaptar uma interface á outra. Fig 12: Padrão Adapter herança múltipla Um

Fig 12: Padrão Adapter herança múltipla

Um adaptador de objeto depende da composição de objetos.

Um adaptador de objeto depende da composição de objetos. Fig. 13: Padrão Adapter composição de objetos

Fig. 13: Padrão Adapter composição de objetos

Participantes

Target (Alvo). Define a interface especifica do domínio que o Cliente usa.

Padrões de Projeto de Software

45

Client (Cliente). Colabora com objetos compatíveis com a interface de Target.

Adaptee(ClasseExistente). Define uma interface existente que necessita ser adaptada.

Adapter (Adaptador). Adapta a interface do Adaptee à interface de Target.

Consequências

Os adaptadores de classes e de objetos têm diferentes soluções de compromisso. Um adaptador de classe:

(herança) adapta Adaptee a Target através de uma classe Adapter concreta. Em consequência, não funciona quando se deseja adaptar uma classe e todas as suas subclasses.

Adapter pode sobrescrever alguns dos comportamentos de Adaptee, uma vez que é um a subclasse de Adaptee.

Introduz apenas um objeto, e não é necessário endereçamento indireto adicional por ponteiros para chegar até o adaptee.

Um adaptador de objetos:

Um único Adapter funciona com muitos Adaptees, isto é, o próprio Adaptee e todas as suas subclasses (se existirem).

O Adapter também pode adicionar funcionalidade a todos os adaptees a cada vez.

É difícil sobrescrever o comportamento do Adaptee. Seria necessário derivar Adaptee e fazer o Adapter referir-se a subclasse ao invés do próprio Adaptee.

Exemplo prático 6

subclasse ao invés do próprio Adaptee. Exemplo prático 6 Fig. 14: Exemplo UML do padrão Adapter

Fig. 14: Exemplo UML do padrão Adapter

Padrões de Projeto de Software

46

Implementação

public

 

interface FileManager {

 

public String open(String s); public String close(); public String read(int pos, int amount, byte[] data); public String write(int pos, int amount, byte[] data);

}

 

FileManager.java

public class FileManagerUtil { private RandomAccessFile f; public boolean openFile(String fileName) { System.out.println("Opening file: "+fileName); boolean success=true; return success;

}

public boolean closeFile() { System.out.println("Closing file"); boolean success=true; return success;

 

}

public boolean writeToFile(String d, long pos, long amount) {

 

System.out.print("Writing "+amount+

 

"

chars from string: "+d);

System.out.println(" to pos: "+pos+" in file");

boolean success=true;

 

return success;

}

public String readFromFile(long pos, long amount) {

 

System.out.print("Reading "+amount+

 

chars from pos: "+pos+" in file"); return new String("dynamite");

"

}

}

 

FileManagerUtil.java

public class FileManagerImpl extends FileManagerUtil implements FileManager { public String close() { return new Boolean(closeFile()).toString();

 

}

public String open(String s) { return new Boolean(openFile(s)).toString();

}

public String read(int pos, int amount, byte[] data) { return readFromFile(pos, amount);

}

public String write(int pos, int amount, byte[] data) { boolean tmp= writeToFile(new String(data), pos, amount); return String.valueOf(tmp);

}

}

 

FileManagerImpl.java

public class FileManagerClient { public static void main(String[] args) { FileManager f = null;

 
 

String dummyData = "dynamite"; f = new FileManagerImpl(); System.out.println("Using filemanager: "+ f.getClass().toString()); f.open("dummyfile.dat"); f.write(0, dummyData.length(), dummyData.getBytes()); String test = f.read(0,dummyData.length(), dummyData.getBytes()); System.out.println("Data written and read: "+test); f.close();

}

}

 

FileManagerClient.java

Padrões de Projeto de Software

47

Exercícios 6.

1. Complete o diagrama de classes abaixo para que um objeto TextView possa participar da aplicação DrawingEditor.

TextView possa participar da aplicação DrawingEditor. 2. Justifique sua escolha pelo tipo de Adapter usado (de

2. Justifique sua escolha pelo tipo de Adapter usado (de classe ou de objeto).

3. Escreva uma classe que permita que o cliente VectorDraw, que já usa a classe Shape, use as operações de RasterBox (e Coords) para obter os mesmos dados, como mostrado na figura a seguir.

obter os mesmos dados, como mostrado na figura a seguir. 2.2.2 Padrão Bridge Propósito Desacoplar a

2.2.2 Padrão Bridge

Propósito

Desacoplar a abstração da sua implementação, de modo que as duas possam variar independentemente.

Motivação

Uma abstração pode ter uma entre varias implementações possíveis, a maneira usual de acomodá-las é usando herança. O uso da herança para

Padrões de Projeto de Software

48

permitir diversas implementações de uma abstração pode não ser suficientemente flexível.

Interface e implementação ficam definitivamente ligadas e não podem ser usadas de modo independente.

Modificar a abstração afeta em geral as implementações, por exemplo: um código cliente deve ser capaz de criar um código de uma “janela”, sem se comprometer com um tipo específico de “janela” (e.g., Xwindow, IBM Presentation Manager, etc.).

O padrão Bridge cria uma hierarquia para as interfaces de “janela” e uma hierarquia para cada implementação de “janela” específica de uma plataforma. A segunda hierarquia deriva de uma classe raiz WindowImp.

A segunda hierarquia deriva de uma classe raiz WindowImp. Fig. 15: Padrão Bridge Aplicabilidade Use o

Fig. 15: Padrão Bridge

Aplicabilidade

Use o padrão Bridge quando:

Desejar evitar um conexão permanente entre abstração e implementação.

Para estender abstrações e suas implementações através de subclasses de modo independente.

Para evitar que mudanças na implementação atinjam os clientes.

Para evitar proliferação de classes (muitas classes de diferentes artefatos para diferentes ambientes em uma única hierarquia).

Deseja compartilhar uma implementação entre múltiplos objetos.

Padrões de Projeto de Software

49

Estrutura

Padrões de Projeto de Software 49 Estrutura Participantes  Abstração . Define a interface da Abstração.

Participantes

Abstração. Define a interface da Abstração.

Abstração Concreta. Estende a interface definida por Abstração.

Implementador. Define a interface para as classes de implementação. Esta interface não precisa corresponder exatamente à interface de Abstração; de fato, as duas interfaces podem ser bem diferentes. A interface de Implementador fornece somente operações primitivas e Abstração define operações de nível mais alto baseado nestas primitivas.

Implementador Concreto. Implementa a interface de Implementador e define sua implementação concreta.

Consequências

Separa interface de implementação.

Melhora a extensão de hierarquias de abstração e implementação (evolução independente).

Esconde detalhes de implementação dos clientes.

Implementação

Apenas um Implementor:

o

é um caso degenerado do Bridge;

o

não precisa criar uma classe Implementor abstrata;

o

ainda assim é útil por separar interface da implementação;

Quando, como e onde decidir qual classe Implementor deve ser instanciada?

o no construtor da abstração, se ela conhece todas as implementações; (possivelmente com base em parâmetros passados ao construtor)

Usar uma implementação default cambiável com o uso;

Usar uma AbstractFactory.

Padrões de Projeto de Software

50

Exemplo prático 7

Padrões de Projeto de Software 50 Exemplo prático 7 /** "Abstraction" */   public interface Shape

/** "Abstraction" */

 

public

interface Shape {

interface Shape {

public void draw(); public void resizeByPercentage(double pct);

}

 

Shape.java

/** "Refined Abstraction" */ public class CircleShape implements Shape { private double x, y, radius; private DrawingAPI drawingAPI;

 

public CircleShape(double x, double y, double radius, DrawingAPI drawingAPI) { this.x = x; this.y = y; this.radius = radius; this.drawingAPI = drawingAPI;

}

// Implementation specific public void draw() {

 

drawingAPI.drawCircle(x, y, radius);

}

// Abstraction specific public void resizeByPercentage(double pct) {

 

radius *= pct;

}

}

 

CircleShape.java

/** "Implementor" */ public interface DrawingAPI { public void drawCircle(double x, double y, double radius);

}

 

DrawingAPI.java

/** "ConcreteImplementor" 1/2 */ public class DrawingAPI1 implements DrawingAPI { public void drawCircle(double x, double y, double radius) { System.out.printf("API1.circle at %f:%f radius %f\n", x, y, radius);

}

}

 

DrawingAPI1.java

/** "ConcreteImplementor" 2/2 */ public class DrawingAPI2 implements DrawingAPI { public void drawCircle(double x, double y, double radius) {

System.out.printf("API2.circle at %f:%f radius %f\n", x, y, radius);

}

}

 

DrawingAPI2.java

public class BridgeExample { public static void main(String[] args) { Shape[] shapes = new Shape[2]; shapes[0] = new CircleShape(1, 2, 3, new DrawingAPI1());

Padrões de Projeto de Software

51

shapes[1] = new CircleShape(5, 7, 11, new DrawingAPI2()); for (Shape shape : shapes) {

shape.resizeByPercentage(2.5);

shape.draw();

}

}

}

BridgeExample.java

Exercícios

1. Considere a hierarquia abaixo.

Exercícios 1. Considere a hierarquia abaixo. Será necessário criar uma subclasse FIFOQueue como

Será necessário criar uma subclasse FIFOQueue como mostrado na figura a seguir

uma subclasse FIFOQueue como mostrado na figura a seguir O que pode ser feito? (Escolha a

O que pode ser feito? (Escolha a melhor alternativa e use, se possível, classes da API Java).

2. Faça um Bridge usando a classe java.util.List como solução de implementação. Implemente os métodos queue(), dequeue(), size() e isEmpty().

os métodos queue(), dequeue(), size() e isEmpty(). 2.2.3 Padrão Composite Propósito Compor objetos em

2.2.3 Padrão Composite

Propósito

Compor objetos em estruturas de árvore para representar hierarquias todo-parte. Composite permite que clientes tratem objetos individuais e composições de objetos de maneira uniforme.

Motivação

Aplicações gráficas, tais como editores de desenhos e sistemas de captura esquemática, permitem aos usuários construir diagramas complexos a partir de componentes simples.

Padrões de Projeto de Software

52

Permitir que objetos do tipo todo ou do tipo parte sejam tratados da mesma maneira. No projeto de um sistema de arquivos, espera-se que tanto um arquivo quanto um diretório possam informar o seu tamanho.

arquivo quanto um diretório possam informar o seu tamanho. Aplicabilidade Use o padrão Composite quando: 

Aplicabilidade

Use o padrão Composite quando:

Para representar hierarquia partes-todo de objetos;

Quando os clientes sejam capazes de ignorar a diferença entre composições de objetos e objetos individuais. Os clientes tratarão todos os objetos na estrutura composta de maneira uniforme.

Estrutura

objetos na estrutura composta de maneira uniforme. Estrutura Participantes  Component (Componente): o Declara a

Participantes

Component (Componente):

o

Declara a interface para os objetos na composição;

o

Implementa comportamento default comum;

o

Declara uma interface para acessar e gerenciar seus componentes filhos;

Leaf (Folha):

Padrões de Projeto de Software

53

o

Representa objetos folha na composição;

o

Define comportamento para objetos primitivos na composição;

Composite (Composição):

o

Define componentes para objetos que têm filhos;

o

Armazena os componentes-filho;

o

Implementa as operações relacionadas com os filhos presentes na interface de Component.

Client (Cliente):

o Manipula objetos na composição através da interface de Component.

Consequências

O padrão composite:

Define hierarquias de classe que consistem de objetos primitivos e objetos compostos;

Estruturas compostas e objetos individuais são tratados de maneira uniforme pelo cliente;

Facilita adicionar novos tipos de componentes;

Pode tornar o projeto excessivamente genérico.

Implementação

Há muitos aspectos a serem considerados quando se implemente este padrão:

Referências explícitas aos pais;

Compartilhamento de componentes;

Maximização da interface de Component;

Declarando as operações de gerência de filhos;

Component deveria implementar uma lista de Components?

Ordenação dos filhos;

Uso de caching para melhorar o desempenho;

Quem deveria deletar componentes?

Qual a melhor estrutura de dados para o armazenamento de componentes?

Exemplo prático 8

public interface AbstractFile { public void ls();

}

AbstractFile.java

public class Directory implements AbstractFile { private String name; private ArrayList<AbstractFile> files = new

ArrayList<AbstractFile>(); private Indentation indentation; public Directory (String name, Indentation indentation) { this.name = name; this.indentation = indentation;

}

public void add(AbstractFile f) {

Padrões de Projeto de Software

54

 

files.add(f);

}

public void ls() { System.out.println(indentation.getIndentation() + name);

indentation.increaseIndentation(); for (AbstractFile file : files) { file.ls();

}

indentation.decreaseIndentation();

}

}

 

Directory.java

class File implements AbstractFile { private String name; private Indentation indentation;

 

public File(String name, Indentation indentation) { this.name = name; this.indentation = indentation;

}

public void ls() { System.out.println(indentation.getIndentation() +

name);

}

}

 

File.java

public class Indentation { private StringBuffer sbIndent = new StringBuffer(); public String getIndentation() { return sbIndent.toString();

 

}

public void increaseIndentation() { sbIndent.append(" ");

}

public void decreaseIndentation() {

if (sbIndent.length() >= 3) {

sbIndent.setLength(sbIndent.length() - 3);

}

}

}

 

Indentation.java

public class ClientComposite {

 

public static void main(String[] args) { Indentation indentation = new Indentation(); Directory dirOne = new Directory("dir111", indentation); Directory dirTwo = new Directory("dir222", indentation); Directory dirThree = new Directory("dir333", indentation); File a = new File("a", indentation); File b = new File("b", indentation); File c = new File("c", indentation); File d = new File("d", indentation); File e = new File("e", indentation); dirOne.add(a); dirOne.add(dirTwo); dirOne.add(b); dirTwo.add(c); dirTwo.add(d); dirTwo.add(dirThree); dirThree.add(e); dirOne.ls();

}

}

 

ClientComposite.java

Exercícios

1. Complete o código que falta no exemplo mostrado na figura a seguir.

Padrões de Projeto de Software

55

Padrões de Projeto de Software 55 2. Que padrão (ões) você usaria para resolver o problema

2. Que padrão (ões) você usaria para resolver o problema abaixo?

O congresso inscreve os participantes, que podem ser um indivíduo ou uma instituição. Cada indivíduo tem um assento.

ou uma instituição. Cada indivíduo tem um assento.  Mostre como implementar uma solução. 3. Implemente

Mostre como implementar uma solução.

3. Implemente o padrão Composite:

a.

Escreva

uma

interface

Publicação

que

trate

de

forma

equivalente

coleções (compostas de outras publicações, como revistas, jornais, cadernos) e artigos individuais indivisíveis.

b.

Escreva uma aplicação de testes que construa o diagrama de objetos a seguir e

uma aplicação de testes que construa o diagrama de objetos a seguir e © Jorge Zavaleta,
uma aplicação de testes que construa o diagrama de objetos a seguir e © Jorge Zavaleta,

Padrões de Projeto de Software

56

• Imprima o número de publicações e de artigos

• Imprima o conteúdo de toString() que deve imprimir o toString de cada publicação (deve conter o nome e, se for artigo, o autor).

2.2.4 Padrão Decorator

Propósito

Anexar responsabilidades adicionais a um objeto dinamicamente. Os Decorators oferecem uma alternativa flexível ao uso de herança para estender uma funcionalidade

Motivação

Adicionar responsabilidades a objetos individuais, e não a toda uma classe. Por exemplo, adição de propriedades, como bordas, ou comportamentos, como rolamento, a componentes de uma interface de usuário, apenas quando for necessário. Uma classe TextView mostra um texto em uma janela, queremos incrementar a janela com uma borda e/ou uma barra de rolagem, pode-se usar herança, mas muitas subclasses incrementos não podem ser acrescentados/removidos em execução.

Aplicabilidade

Use Decorator:

Para adicionar responsabilidades a objetos individuais de forma dinâmica e transparente, isto é, sem afetar outros objetos.

Para retirar responsabilidades.

Quando a extensão através do uso de subclasses não é pratica. Às vezes, um grande número de extensões independentes é possível e isso poderia produzir uma explosão de subclasses para suportar cada combinação. Ou a definição de uma classe oculta ou indisponível para utilização de subclasses.

Estrutura

oculta ou indisponível para utilização de subclasses. Estrutura © Jorge Zavaleta, 2013 zavaleta.jorge@gmail.com

Padrões de Projeto de Software

57

Participantes

Componente: define a interface para objetos que podem ter responsabilidades acrescentadas dinamicamente;

ComponenteConcreto: define um objeto para o qual responsabilidades adicionais podem ser atribuídas;

Decorador: define uma interface que segue a de Componente e mantém uma referência para um objeto Componente;

DecoradorConcreto: acrescenta responsabilidades ao componente.

Consequências

Maior flexibilidade que herança: decoradores podem ser retirados e acrescentados em execução, Mas ponteiros para a TextView continuam enxergando o objeto sem decoração.

Não há necessidade de criação de inúmeras subclasses.

Um decorador e seus componentes não são iguais: operações de igualdade falham.

Muitos objetos pequenos são criados.

Implementação

Vários tópicos deveriam ser levados em conta quando se aplica o padrão Decorator.

Conformidade de interface;

Omissão da classe abstrata Decorator;

Mantendo leves as classes Component;

Mudar o exterior de um objeto versus mudar o seu Strategy ).

Exemplo prático 9

interior (Decorator versus

mudar o seu Strategy ). Exemplo prático 9 interior (Decorator versus © Jorge Zavaleta, 2013 zavaleta.jorge@gmail.com

Padrões de Projeto de Software

58

public interface Logger { public void log(String msg);

 

}

 

Logger.java

public class LoggerDecorator implements Logger { Logger logger; public LoggerDecorator(Logger logger) { super(); this.logger = logger;

 

}

public void log(String msg) { logger.log(msg);

}

}

 

LoggerDecorator.java

public class ConsoleLogger implements Logger { public void log(String msg) { System.out.println("Detta ska skrivas till consolen! "+ msg);

 

}

}

 

ConsoleLogger.java

public class EncryptLogger extends LoggerDecorator { public EncryptLogger(Logger logger) { super(logger);

 

}

public void log(String msg) {

msg = encrypt(msg); logger.log(msg);

}

private String encrypt(String msg) { msg = msg.substring(msg.length()-1) + msg.substring(0,

msg.length() -1);

return msg;

}

}

 

EncryptLogger.java

public class HTMLLogger extends LoggerDecorator { public HTMLLogger(Logger logger) { super(logger);

 

}

public void log(String msg) { msg = makeHTML(msg); logger.log(msg);

}

private String makeHTML(String msg) { msg = "<html><body>" + "<b>" + msg + "</b>" +

"</body></html>";

return msg;

}

}

 

HTMLLogger.java

public class LoggerFactory { public static final String TYPE_CONSOL_LOGGER = "console"; public static final String TYPE_FILE_LOGGER = "file"; public Logger getLogger(String type) { if(TYPE_CONSOL_LOGGER.equals(type)) { return new ConsoleLogger(); } else { return new FileLogger();

 

}

}

}

 

LoggerFactory.java

public class FileLogger implements E1Logger{ public void log(String msg) { System.out.println("Dentro do FileLogger! "+msg);

}

}

 

FileLogger.java

public class DecoratorClient {

 

Padrões de Projeto de Software

59

public static void main(String[] args) { LoggerFactory factory = new LoggerFactory(); Logger logger = factory.getLogger(LoggerFactory.TYPE_FILE_LOGGER); HTMLLogger htmlLogger = new HTMLLogger(logger); htmlLogger.log("A message to log"); EncryptLogger encryptLogger = new EncryptLogger(logger); encryptLogger.log("A message to log");

}

}

DecoratorClient.java

Exercícios

1. Crie um objeto simples que armazene um texto que possa ser recuperado com um método getTexto(). Crie decoradores que retornem o texto: a) em caixa-alta, b) invertido e c) cercado por tags <b> e </b>. Teste os decoradores individualmente e em cascata.

2. Crie um decorador ComandoReader que possa decorar um Reader. O objeto não deve alterar o comportamento dos métodos read() originais mas deve oferecer um método readComando() que retorna um objeto Command

O objeto Command deve ser construído a partir do stream recebido. Podem ser cinco tipos: NullCommand, NewCommand, DeleteCommand, GetCommand e GetAllCommand

Os strings de entrada devem vir no formato <comando> <um ou mais espaços em branco> <argumentos>. O número de argumentos esperados depende do comando: 1) new id nome, 2) delete id, 3) get id, 4) all. Comandos incorretos ou desconhecidos retornam NullCommand.

3. Teste o ComandoReader passando-lhe um stream de caracteres (leia um string como um stream).

2.2.5 Padrão Façade

Propósito

Oferecer uma interface única para um conjunto de interfaces de um subsistema. Façade define uma interface de nível mais elevado que torna o subsistema mais fácil de usar.

Motivação

Necessidade de estruturar um sistema em subsistema, facilitando o acesso e minimizando a comunicação e dependências entre os subsistemas.

Aplicabilidade

Use padrão Façade quando:

Padrões de Projeto de Software

60

Fornecer uma interface simples e unificada para um sistema complexo, desacopla os subsistemas dos clientes, promovendo-se a independência e portabilidade dos subsistemas, estruturando o sistema em camadas.

Estruturar um sistema em subsistemas ajuda a reduzira complexidade. Um objetivo comum a todos os projetos é minimizar a comunicação e as dependências entre os subsistemas que compõem uma aplicação. Uma maneira de alcançar este objetivo é introduzir um objeto façade (fachada)

Existem muitas dependências entre os clientes e as classes de implementação de uma abstração. Ao introduzir uma façade para desacoplar o subsistema dos clientes e de outros subsistemas, se promove a independência e portabilidade dos sistemas.

Estrutura

a independência e portabilidade dos sistemas. Estrutura Participantes  Façade . Conhece quais as classes do

Participantes

Façade. Conhece quais as classes do subsistema responsáveis pelo atendimento de uma solicitação.

Classes de subsistema. Implementa a funcionalidade do subsistema. Encarrega- se do trabalho atribuído a elas pelo objeto Façade. Não tem conhecimento da Façade, isto é, elas não mantêm referências para a mesma.

Consequências

O padrão Façade oferece os seguintes benefícios:

Isola os clientes dos componentes de um subsistema, reduzindo o número de objetos com os quais os clientes têm que lidar, e tornando, assim, mais fácil o uso de tal subsistema.

Padrões de Projeto de Software

61

Promove o fraco acoplamento entre um subsistema e os seus clientes. Esta

característica permite variar os componentes de um subsistema sem afetar os seus clientes. Simplifica o porte de um sistema para outras plataformas, uma vez

a sua utilização diminui a ocorrência de alterações em cascata em função da

necessidade de uma alteração em certo subsistema.

Não impede que as aplicações utilizem diretamente as classes de um subsistema caso necessitem fazê-lo. Assim, pode-se escolher entre a facilidade de uso e uma maior flexibilidade na manipulação das funcionalidades fornecidas por um subsistema.

Implementação

Considere os seguintes aspectos quando implementar uma façade:

Redução do acoplamento cliente-subsistema. O acoplamento entre os clientes e

o subsistema pode ser ainda mais reduzido tornando Façãde uma classe abstrata com subclasses concretas para diferentes implementações de um subsistema.

Classes de subsistemas: públicas e privadas?

Exemplo prático 10

public class Cubo { public int returnCubo(int x){ return x * x * x;

}

}

Cubo.java

public class Duplica { public int returnDouble(Cubo cubo, int x) { return 2 * cubo.returnCubo(x);

}

}

Duplica.java

public class Facade { public int cubeX(int x) { Cubo cubo = new Cubo(); return cubo.returnCubo(x);

}

public int cubeXTimes2(int x) { Cubo cubo = new Cubo(); Duplica duplica = new Duplica(); return duplica.returnDouble(cubo, x);

}

public int xToSixthPowerTimes2(int x) { Cubo cubo = new Cubo(); Duplica duplica = new Duplica(); DuplicaSixPower six = new DuplicaSixPower(); return six.doMoreStuff(cubo, duplica, x);

}

}

Facade.java

public class DuplicaSixPower { public int doMoreStuff(Cubo cubo, Duplica doble, int x) { return cubo.returnCubo(x)*doble.returnDouble(cubo, x);

}

}

DuplicaSixPower.java

public class ClienteFacade { public static void main(String[] args) { Facade facade = new Facade();

Padrões de Projeto de Software

62

int x = 3; System.out.println("O Cubo de " + x + " = " + facade.cubeX(3)); System.out.println("O Cubo de " + x + " multiplicando por 2 = " + facade.cubeXTimes2(3)); System.out.println(x + " elevado a sexta potencia multiplicado por 2 = " +

facade.xToSixthPowerTimes2(3));

}

}

ClienteFacade.java

Exercícios

1. A aplicação abaixo tem uma GUI. Será necessário implementar uma UI orientada a caracter e uma interface Web. O que poderia ser usado para reduzir a duplicação de código e tornar a utilização das classes envolvidas mais simples? Implemente!

das classes envolvidas mais simples? Implemente! 2. Qual a diferença entre Façade e Adapter (se você

2. Qual a diferença entre Façade e Adapter (se você tiver um Façade para um único objeto?)

2.2.6 Padrão Flyweight

Propósito

Usar compartilhamento para suportar grandes quantidades de objetos refinados eficientemente.

Motivação

Em um editor de textos certas vantagens são obtidas ao representar cada caractere como objeto.

Entretanto, seria necessária a instanciação de um número consideravelmente alto de objetos, com consumo excessivo de memória e overhead de execução.

Um flyweight é um objeto compartilhado que pode ser utilizado simultaneamente em múltiplos contextos.

Padrões de Projeto de Software

63

Atua como um objeto independente em cada contexto, de modo que os clientes não são cientes do compartilhamento.

O

conceito chave é a distinção entre estado intrínseco e estado extrínseco:

 

o

O estado intrínseco é armazenado no flyweight e consiste de informações independentes do contexto e, portanto, compartilháveis.

o

O estado extrínseco depende de e varia com o contexto e, portanto, não pode ser compartilhado. Os clientes são responsáveis por passar o estado extrínseco para o flyweight, quando necessário.

Aplicabilidade

A eficiência do padrão Flyweight depende muito de como onde ele é usado.

Aplique o padrão Flyweight quando todas as condições a seguir forem verdadeiras:

A

aplicação usa um grande número de objetos.

Custos de armazenamento são altos devido ao grande número de objetos.

O

estado da maior parte dos objetos pode ser definido de forma extrínseca.

Se o estado extrínseco for removido muitos grupos de objetos podem ser substituídos por um número relativamente pequeno de objetos compartilhados.

A aplicação não depende da identidade dos objetos. Uma vez que objetos Flyweights podem ser compartilhados, testes de identidade retornarão verdadeiro para objetos conceitualmente diferentes.

Estrutura

para objetos conceitualmente diferentes. Estrutura Participantes © Jorge Zavaleta, 2013

Participantes

Padrões de Projeto de Software

64

Flyweight: Declara uma interface através da qual Flyweights podem receber estado extrínseco e atuar com base nele.

ConcreteFlyweight (Caractere): Implemente a interface Flyweighte adiciona o armazenamento do estado intrínseco, se existir. O objeto deve ser compartilhável – qualquer estado que ele armazenar deve ser independente do contexto do ConcreteFlyweight.

UnsharedConcreteFlyweight (Row, Column): Nem todas as subclasses de Flyweightprecisam ser compartilhadas. É comum que objetos UnsharedConcreteFlyweight contenham, como filhos, objetos ConcreteFlyweightde algum nível da hierarquia de Flyweights.

FlyWeightFactory: Cria e gerencia objetos Flyweight. Garante que os Flyweights são compartilhados de forma apropriada. Quando o cliente solicita um Flyweight ele devolve um já existente ou cria um novo.

Client: Mantém referências a Flyweights. Calcula ou armazena o estado extrínseco dos Flyweights.

Consequências

Podem adicionar custos de runtime, devido à transferência, descoberta ou computação do estado extrínseco.

Tais custos são, entretanto, compensados pelas economias no armazenamento, que é influenciado por alguns fatores:

o

Redução do número total de instâncias propiciada pelo compartilhamento.

o

Tamanho do estado intrínseco por objeto.

o

Forma de obtenção do estado extrínseco (armazenado ou calculado).

Quanto maior o compartilhamento maior a economia no armazenamento.

Quanto maior o tamanho do estado intrínseco (compartilhado) do objeto maior a economia no armazenamento.

Esta economia é ainda maior quando o estado extrínseco é computado ao invés de armazenado.

Implementação

Considere os seguintes aspectos ao implementar o padrão Flyweight:

Reduzindo o estado extrínseco:

o

Remover estado extrínseco não irá ajudar a reduzir o custo de armazenamento se existir tantos tipos diferentes de estado quanto o número de instâncias antes do compartilhamento

o

O estado extrínseco pode ser computado a partir de uma estrutura separada, com menores demandas de armazenamento:

Padrões de Projeto de Software

65

Ex: A fonte e o estilo de cada caracter do texto podem ser armazenados em um mapa separado que rastreia os caracteres com os mesmos atributos tipográficos.

Como os textos utilizam uma quantidade menor de fontes e estilos do que caracteres diminui-se a demanda por armazenamento.

Gerenciando objetos compartilhados:

o

Clientes não devem instanciar objetos diretamente e sim através do FlyweightFactory.

o

Geralmente a fábrica utiliza um container associativo (ex: mapa cuja chave é o caractere e o valor é o Flyweight).

o

Pode ser necessário reference counting ou garbage collection quando o número de Flyweightsnão é fixo e não é pequeno.

Exemplo prático 11

public interface Flyweight { public void doMath(int a, int b);

}

Flyweight.java

public class FlyweightAdder implements Flyweight { String operation; public FlyweightAdder() { operation = "Somando"; try {

Thread.sleep(3000);

}

}

catch (InterruptedException e) { e.printStackTrace();

}

@Override public void doMath(int a, int b) { System.out.println(operation + " " + a + " + " + b + ": " + (a + b));

}

}

FlyweightAdder.java

public class FlyweightMultiplier implements Flyweight { String operation; public FlyweightMultiplier() { operation = "multiplicando"; try {

Thread.sleep(3000);

}

}

catch (InterruptedException e) { e.printStackTrace();

}

@Override public void doMath(int a, int b) { System.out.println(operation + " " + a + " * " + b + ": " + (a * b));

}

}

FlyweightMultiplier.java

public class FlyweightFactory { private static FlyweightFactory flyweightFactory; private Map<String, Flyweight> flyweightPool; private FlyweightFactory() { flyweightPool = new HashMap<String, Flyweight>();

}

public static FlyweightFactory getInstance() {

Padrões de Projeto de Software

66

 

if (flyweightFactory == null) { flyweightFactory = new FlyweightFactory();

}

return flyweightFactory;

}

 

public Flyweight getFlyweight(String key) {

if (flyweightPool.containsKey(key)) { return flyweightPool.get(key);

} else {

Flyweight flyweight; if ("add".equals(key)) { flyweight = new FlyweightAdder();

} else {

 

flyweight = new FlyweightMultiplier();

 

}

flyweightPool.put(key, flyweight); return flyweight;

}

}

}

 

FlyweightFactory.java

public class ClientFlyweight { public static void main(String[] args) { FlyweightFactory flyweightFactory = FlyweightFactory.getInstance(); for (int i = 0; i < 5; i++) { Flyweight flyweightAdder = flyweightFactory.getFlyweight("add"); flyweightAdder.doMath(i, i); Flyweight flyweightMultiplier = flyweightFactory.getFlyweight("multiply"); flyweightMultiplier.doMath(i, i);

 

}

}

}

 

ClientFlyweight.java

Exercícios

1. Implemente uma aplicação que imprima aleatoriamente 10 números de 10 algarismos:

Cada algarismo deve ser uma instancia do objeto Algarismo que contém o número 1, 2, 3, etc. como membro imutável.

Use Flyweight para construir um cache de objetos para que objetos que representam o mesmo algarismo sejam reutilizados.

2. Implemente um cache de fatoriais:

Cada fatorial de n equivale a n*fatorial(n-1). Use um cache para aproveitar fatoriais já calculados.

Escreva uma aplicação que calcule fatoriais de 0 a 15.

2.2.7 Padrão Proxy

Propósito

Disponibilizar um substituto para outro objeto, controlando o acesso a ele.

Motivação

Adiar o custo de criação e inicialização de um objeto até o momento em que ele é realmente necessário.

Padrões de Projeto de Software

67

Ex: um editor de textos pode não carregar todas as imagens do texto no momento em que ele é aberto, somente aqueles atualmente visíveis na aplicação.

Cria-se os objetos sob demanda.

Entretanto, o que colocar no lugar da imagem para indicar a sua presença no texto?

Como se pode abstrair a criação sob demanda da imagem, de modo que o código da implementação do editor (ex: renderização e formatação de texto) seja mantido simples?

A solução é utilizar um objeto procurador (proxy) que atua no lugar da imagem real.

O proxy cria a imagem real somente quando o editor solicita a sua exibição.

O proxy armazena uma referência para a imagem e as suas dimensões, de modo que o algoritmo de formatação funcione mesmo sem conhecer a imagem real.

Aplicabilidade

Útil sempre que existe a necessidade de uma referência para objeto que seja mais sofisticada e versátil do que um ponteiro simples. Alguns exemplos: