Buscar

Revisao de padrões de projeto de software

Prévia do material em texto

Fundamentos de padrões de projeto
Padrões GoF:public class Montadora
{
public void novoCarro(String tipo)
{
     Carro carroZero;
if(tipo.equals(“Vectra”))
      carroZero = new Vectra();
else
if(tipo.equals(“Omega”))
carroZero = new Omega();
else 
 if(tipo.equals(“Gol”))
   carroZero = new Gol();
else 
if(tipo.equals(“Golf”))
    carroZero = new Golf();
}
};
O padrão FactoryMethod é caracterizado por retornar uma instância de uma classe, de acordo com parâmetros informados para o método, dessa forma, ao invés do cliente (ou programador) instanciar classes utilizando o operador new, ele chama um método genérico de criação apropriado. Por exemplo, considere uma superclasse Carro com quatro classes derivadas: Vectra, Omega, Gol e Golf. Suponha agora que é necessário instanciar objetos da classe Carro em um método pertencente a classe Montadora. A sequência de comandos a seguir apresenta uma solução comumente aceita e praticada por inúmeros 
Padrões de Criação: Fornecem um guia de como instanciar objetos. Esta ação normalmente envolve decisões dinâmicas para escolher, por exemplo, qual classe instanciar ou a quais objetos delegar responsabilidade. Esse padrão nos mostra como estruturar e encapsular essas decisões. Há cinco padrões de criação GoF: Abstract Factory, Builder, Factory Method, Prototype e Singleton.
2. Padrões Estruturais: Definem caminhos comuns para a organização de diferentes tipos de objetos, facilitando sua integração e colaboração mutua. Há sete padrões estruturais GoF: Adapter,  Bridge, Composite, Decorator, Façade, Flyweight e Proxy
3. Padrões Comportamentais: São projetados para organizar, gerenciar e combinar diferentes comportamentos. Há 11 padrões comportamentais GoF: Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method e Visitor.
O padrão AbstractFactory apresenta um nível de abstração superior quando comparado ao padrão FaxtorMethod. Apresenta como uma de suas vantagens controlar as classes de objetos criadas por uma aplicação, restringindo o acesso as classes concretas e isolando os clientes das classes de implementação.
Além disso, como as fábricas abstratas aparecem apenas uma vez na aplicação, no momento de sua instanciação, tem-se facilidade para utilizar diferentes configurações, simplesmente localizando e trocando a fábrica abstrata utilizada por outra.
O primeiro grupo, formado pela classe Carro e suas classes derivadas, representa os diferentes produtos a serem criados pelas fabricas concretas, não há informações para diferenciar os produtos conforme seu fabricante, esta diferenciação é feita pelas fábricas.
O segundo grupo, formado pelas classes Montadora e suas classes derivadas,implementam a interface e as operações que criam os produtos concretos, associados com cada tipo de fábrica. Para utilizar instanciar um determinando carro, instancia-se primeiro o fabricante e a seguir o modelo do carro desejado.
A imagem apresenta o diagrama UML com um exemplo de utilização do padrão AbstractFactory na implementação de um conjunto de classes para instanciar carros de acordo com seu respectivo fabricante, semelhante ao exemplo anterior, sobre FactoryMethod. Porém, diferente do anterior, a implementação prevê dois grupos de classes de carros: Volkswagem e General Motors, conforme a estrutura do padrão presentes no diagrama.
Fazendo analogia com a estrutura do padrão AbstractFactory apresentado em Gamma et al (2000), tem-se as seguintes correspondências:
Aula 2 - PADRÕES DE CRIAÇÕES
 O padrão de projeto Builder é utilizado na instanciação de objetos complexos. Portanto, inicialmente, é preciso entender como caracterizar um objeto complexo. Um objeto é caracterizado como complexo quanto ele é associado a outros objetos por qualquer tipo de relacionamento conhecido ou ainda quando ele apresenta atributos que são instanciados a partir de diferentes regras de negócio. Nestas circunstâncias, instanciar objetos não é trivial, diferentes condições e situações devem ser consideradas.
O padrão Builder é bastante utilizado nestas situações, podendo ser visto como um construtor especializado, que irá executar e avaliar diferentes regras de negócio e demais particularidades relacionadas com a criação de um novo objeto. A ideia é atribuir a responsabilidade de criação do objeto complexo a outra classe, esta classe irá armazenar todas as configurações e regras necessárias para a instanciação do novo objeto.
Uma solução natural, apresentada por muitos desenvolvedores, é embutir no construtor da própria classe a lógica para criação de um objeto ou ainda distribuir a lógica de criação em vários métodos adicionais. Um dos principais objetivos do padrão Builder é evitar este tipo de estratégia, seu propósito é separar o algoritmo de criação de um objeto complexo tanto da especificação, quanto das partes que o compõem. Esta abordagem facilita a criação de objetos com diferentes configurações e representações, facilita a manutenção do código, reduz a complexidade das classes relacionadas (a complexidade de criação é atribuída ao Builder) e melhora a legibilidade da solução final, ou seja, para entender como um objeto é criado e sob quais condições, deve-se avaliar a classe Builder responsável por esta ação.
Exemplificando Builder
Como um exemplo, vamos considerar a necessidade de instanciar um objeto da uma classe Pedido, a qual é formada por um conjunto de itens de pedido (Produtos) e por outros atributos específicos, tais como numero, data e vendedor. A imagem apresenta o diagrama de classes em UML com a solução proposta utilizando o padrão Builder. Inicialmente, foram projetadas as três classes básicas relacionadas com o problema: Produto, Pedido e Vendedor. Após, foi projetada a classe PedidoBuilder para realizar a instanciação de objetos Pedido. Para efeito de simplificação da interface da classe PedidoBuilder, todos os parâmetros necessários para instanciar uma classe Pedido foram agrupados no atributo parâmetros do tipo String.
De acordo com a estrutura proposta, o método buildPedido é responsável por chamar cada método da classe Pedido e assim gerar um objeto completo, enquanto que o método getPedido retorna o objeto Pedido construído pelo método buildPedido.
Observe que o método buildPedido realiza todas as chamadas necessárias para a criação completa de uma instância do objeto Pedido. Se por ventura, futuramente, a classe Pedido sofrer alguma alteração, somente o método buildPedido da classe PedidoBuilder deverá ser alterado. A seqüência de comandos abaixo ilustra como é a criação de um objeto Pedido usando a classe pedidoBuilder.
Comparando o diagrama de classe apresentado na imagem com a estrutura do padrão de projeto Builder proposta em Gamma et al. (2000), tem-se que a classe PedidoBuilder corresponde a ConcreteBuilder, Pedido corresponde a Product e Director será qualquer classe que utilizará o padrão Builder para instanciar objetos Pedido. A classe Builder especifica uma interface abstrata, definindo as operações básicas que devem ser implementadas pelas classes derivadas.
Por fim, é importante destacar a diferença entre o padrão AbstractFactory, estudado na aula anterior, e o padrão Builder, pois ambos estão direcionados a criação de objetos. A principal diferença entre eles é que o Builder constrói objetos complexos passo-a-passo, retira-se o algoritmo de criação do construtor da classe do objeto e criar-se uma nova classe com esta responsabilidade, enquanto que AbstractFactory constrói famílias de objetos relacionados, simples ou complexos, de uma só vez, primeiro instancia-se a família do objeto e a seguir o objeto propriamente dito.
O padrão de projeto Prototype declara um método chamado clone na superclasse abstrata do modelo (Prototype). Em função desta declaração, cada classe concreta derivada deve ser capaz de gerar uma nova instância de si próprio, ou seja, um método especializado capaz de construir objetosiguais a si mesmo, também podendo ser chamado de clone.
Entre as aplicações para este padrão de projeto estão a facilidade de instanciar classes em tempo de execução. Quando as instancias de uma classe apresentam poucas combinações de estados, pode ser mais conveniente definir previamente um conjunto de protótipos e cloná-los, sempre que foi necessário. Além disso, é bastante útil para guardar o estado de um objeto em determinados momentos, nesses casos, basta pedir uma cópia do próprio objeto, ao invés de criar uma nova instancia manualmente.
Exemplificando Prototype
A imagem ilustra um exemplo de aplicação do padrão Prototype. A classe CarroClone é uma interface que declara o método clone, a ser implementado nas classes derivadas. Desse modo, a classe Carro implementa este método retornando uma nova instancia da classe a partir dos valores atuais dos seus atributos. 
A implementação deste padrão de projeto é facilitada na linguagem de programação Java com a utilização da interface Cloneable, utilizada apenas para indicar que o método Object.clone() pode realizar uma cópia, atributo por atributo, das instâncias da classe.
O padrão de projeto Singleton é um dos padrões mais conhecidos e extensivamente utilizados em programação orientada a objetos. Está direcionado para as situações onde precisamos manter uma única instancia de uma classe durante toda a execução da aplicação. Por exemplo, para garantir apenas um spooler de impressão, embora tenhamos diversas impressoras em um sistema, para armazenar uma conexão com uma base de dados que será chamadas diversas vezes, para armazenar o log da execução de uma aplicação, para centralizar os dados de configuração de um sistema, para armazenar os dados do usuário que está logado em um sistema, entre outros.
AULA 3 – PADRÕES ESTRUTURAIS GOF
Programadores se deparam muitas vezes com a situação onde é preciso acrescentar responsabilidades a objetos e não a classe. Uma das opções é utilizar herança, entretanto, esta alternativa é estática. Outra alternativa é especificar uma nova classe para implementar cada responsabilidade que se deseja atribuir dinamicamente a um dado objeto. Esta nova classe se chama Decorator.
Exemplificando Decorator
No exemplo apresentado na imagem utiliza-se como exemplo a montagem uma massa em um sistema de fast food. Inicialmente, escolhe-se o tipo da massa e a seguir escolhem-se os acompanhamentos (Decorator). Nesse caso, a implementação utilizando herança seria bastante complexa, iria requerer uma série de combinações diferentes. Na modelagem apresentada, primeiro escolhe-se o tipo de massa e a seguir acrescentam-se os acompanhamentos, cada qual, será adicionado dinamicamente ao objeto massa que foi previamente criado.
No exemplo apresentado, há quatro tipos de massas derivadas da classe abstrata Massa: Ravioli, Canelone, Spaghetti e Penne. Além de uma classe abstrata CondimentosDecorator que possui cinco classes derivadas: Molho4Queijos, MolhoSugo, MolhoBranco, Carne e Vegetais. Cada uma destas classes apresenta um método construtor que recebe os seguintes parâmetros: massa, descrição e custo do acompanhamento. 
Assim, uma vez instanciado um objeto Massa, pode-se adicionar dinamicamente diferentes condimentos. O trecho de código Java a seguir ilustra o funcionamento do método construtor das classes derivadas de CondimentosDecorator, fundamental para entender o funcionamento do padrão.
O padrão de projeto Composite é utilizado, portanto, quando se pretende representar hierarquias partes-todo (ou todo-parte) de objetos, ou ainda, quando se pretende modelar relacionamento de agregação. A vantagem de utilizar este padrão é que o cliente irá acessar objetos compostos ou não de maneira uniforme, pois irá se relacionar com a classe abstrata Component. Ver, por exemplo, o método print do exemplo apresentado na Figura 2, quando se trata de uma parte, o processamento é realizado diretamente pela superclasse Componente, quando se trata de uma composição, o processamento é feito pela classe Composite, imprimindo todas as partes que compõem o objeto.
O padrão Adapter é bastante utilizado para compatibilizar classes implementas por programadores diferentes, ou desenvolvidas em momentos diferentes, ou ainda para unir classes com interfaces diferentes em uma estrututura hierarquica única, sem precisar implementar novamente todas as funcionalidades e interfaces da classe já existente. A ideia é criar uma nova classe (Adapter) com a interface padrão que se deseja para fazer a conexão com a classe já existente (Adaptee) com interface diferente da estrutura de classes que se pretende utilizar no sistema.
Bridge
Desenvolver aplicações portáveis é um desejo de todo programador, permitir que o sistema consiga se adaptar dinamicamente a diferentes plataformas é mais desejável ainda. O padrão Bridge tem potencial para auxiliar nessas situações. A solução natural quando se pretende desenvolver uma determinada solução para duas plataformas diferentes é utilizar herança, implementando uma classe derivada para cada situação. No entanto, assim como discutido na seção sobre o padrão Decorator, esta abordagem relaciona uma abstração (entende-se definição dos métodos e atributos) a sua implementação permanentemente, oferecendo pouca flexibilidade para mudanças e reutilização da das abstrações e implementações de forma independente.
AULA 4 – PADRÕES ESTRUTURAIS GOF
Em muitas situações de programação, é possível resolver o problema de acesso a um determinado objeto com a utilização de uma referência a variável desejada, este é um mecanismo de simples utilização e bastante conhecido pelos desenvolvedores de software. No entanto, quando é necessário utilizar um mecanismo mais versátil e sofisticado, por exemplo, para postergar a instanciação de um objeto, para controlar o acesso ao mesmo, para acessá-lo em uma máquina remota ou mesmo para gravá-lo em uma base de dados, a alternativa de utilizar uma referência ao objeto não é mais apropriada.
Façade
Há muitas aplicações de software, onde os programadores dividem um problema complexo em diversas classes para resolvê-lo de forma gradual e modular. No entanto, embora essa prática seja bastante comum e recomendada pelos autores da área de engenharia de software, não se consegue vislumbrar rapidamente quais funcionalidades de cada classe estão relacionadas com o problema principal e quais são os procedimentos auxiliares de cada classe, dificultando a utilização da solução proposta pelas classes clientes.
A classe Façade, em qualquer contexto, tem como objetivo mais amplo buscar respostas para um problema de forma transparente em um conjunto de subsistemas e fornecer uma resposta imediata para a classe cliente. Entre as principais vantagens desse padrão de projeto pode-se citar:
Tornar os clientes independentes da complexidade dos diferentes subsistemas de uma aplicação.
Incentivar acoplamento fraco entre o subsistema e seus clientes.
Ajudar a melhorar portabilidade dos sistemas.
Simplificar o acesso a determinadas funcionalidades de um subsistema, sem inviabilizar sua utilização direta.
Flyweight
Antes de qualquer comentário sobre o padrão de projeto Flyweight, é importante destacar as palavras granularidade fina na definição acima, elas são fundamentais para entender o escopo e funcionalidade deste padrão de projeto, pois, este padrão está relacionado a objetos simples e com poucas funcionalidades. Além disso, normalmente, esse padrão de projeto está relacionado àqueles objetos que aparecem em grande número na aplicação, que precisam ser instanciados inúmeros vezes com os mesmos atributos.
AULA 5 PADRÕES COMPORTAMENTAIS GOF (I)
O padrão de projeto Intepreter descreve como projetar um conjunto de classes para representar e interpretar uma gramática para linguagens simples. Sua aplicação é recomenda naquelas situações em que há necessidade de interpretar uma linguagem qualquer e, ao mesmo tempo, quando se quer representar sentenças da linguagem, como árvoresabstratas sintáticas. É importante destacar que o termo “linguagem”, na definição desse padrão, é bastante ampla, não estando restrita apenas a linguagens de programação, como na maioria dos exemplos apresentados.
Para ampliar nossa visão, o exemplo apresentado na Figura 1 apresenta uma aplicação bastante didática, embora pouco usual, para exemplificar os elementos principais desse padrão de projeto. No exemplo apresentado, procura-se demonstrar também a estreita relação do padrão Interpreter com o padrão de projeto Composite, apresentado na Aula 3, compare os exemplos e verifique as semelhanças de modelagem, procure também reconhecer que a árvore sintática abstrata do padrão de projeto Interpreter é uma instância do padrão Composite.
A estrutura do padrão Interpreter sugere que a modelagem do problema seja realizada através de uma gramática recursiva. Nesse caso, cada regra gramatical pode ser representada através de dois elementos principais: (1) como um “Composite” e (2) como um terminal, ou seja, um nodo folha em uma estrutura de árvore. O padrão Interpreter realiza sucessivas chamadas recursivas ao padrão Composite (nodos não terminais) para interpretar as sentenças, até chegar em nodos terminais.
O exemplo apresentado na Figura 1 utiliza o padrão de projeto Interpreter para representar e interpretar uma música. Inicialmente, é definido uma classe abstrata Melodia (AbstractExpression), a partir da qual são definidas duas classes derivadas: Partitura e NotaMusical. Uma partitura é uma representação formal e escrita de uma música que dispõe de diferentes símbolos (notas musicais) que estão associados a sons. Dessa forma, para “tocar” uma música, basta percorrer a estrutura de dados formada com a utilização do padrão de projeto Interpreter e interpretá-la, tocando os sons conforme os dados armazenados na classe NotaMusical.
Mais uma vez, cabe destacar que é importante visualizar, a partir do exemplo anterior, que a estrutura do padrão Interpreter sugere que a modelagem do problema seja realizada através de uma gramática recursiva. Caso o leitor não tenha familiaridade com estes conceitos, normalmente relacionados com a área de Compiladores e Linguagens Formais, procure alguma referência sobre este assunto ou consulte Aho et al. (1995).
Para Gamma et al. (2000) entre as vantagens do padrão Interpreter podem-se citar: (1) facilidade para mudar e estender a gramática, pois, o padrão utiliza classes para representar as regras gramaticais. Pode-se usar herança para mudar ou estender a gramática, assim como expressões existentes podem ser modificadas incrementalmente e novas expressões podem ser criadas a partir das existentes; (2) a implementação da gramática é mais fácil, pois, classes que definem os nós folhas da árvore tem implementações similares. Por outro lado, uma das desvantagens é que o padrão Interpreter define, ao menos, uma classe para cada regra da gramática, logo, gramáticas com muitas regras são difíceis de manter e administrar.
O padrão de projeto Template Method sugere a implementação de um algoritmo que faz uso de diferentes métodos, diferenciando das soluções convencionais por deixar o esqueleto deste algoritmo fixo em uma superclasse abstrata. Os demais métodos, chamados na definição do padrão de PrimitiveOperation, podem ser redefinos nas classes derivadas para oferecer diferentes alternativas de implementação do “esqueleto” proposto, ou seja, implementar um comportamento concreto aos métodos que foram definidos como abstratos na superclasse.
A vantagem dessa estrutura é que se consegue alterar o comportamento de um algoritmo, sem modificar sua estrutura lógica. Dessa forma, as partes invariantes de um algoritmo são implementadas uma única vez na superclasse (abstrata), enquanto que as partes que podem variar, conforme a situação, contexto ou problema a ser resolvido, são redefinas nas classes derivadas (concretas).
A imagem apresenta um exemplo para demonstrar a aplicação desse padrão de projeto em um algoritmo que tem como objetivo validar um usuário para acesso a uma aplicação. Inicialmente, foi especificado uma superclasse abstrata chamada ValidarUsuario (AbstractClass) e duas classes derivadas, a primeira chamada PessoaFisica (ConcretClass) e a segunda chamada PessoaJuridica (ConcretClass).
A implementação do padrão de projeto Chain of Responsability requer que cada objeto receptor de uma determinada solicitação tenha uma lógica para descrever os tipos de solicitação que é capaz de processar e como passar adiante aquelas que requeiram processamento por outros receptores. A delegação das solicitações pode formar uma árvore de recursão, com um mecanismo especial para inserção de novos receptores no final da cadeia existente.
A classe Handler
Utilizando a estrutura do padrão de projeto Chain of Responsability, definiu-se, inicialmente, uma classe abstrata chamada Handler com um método responsável por atender requisições (atenderRequisicao) e outro para setar o sucessor (setSucessor). Vejam que o sucessor é uma referência para a própria classe, dessa forma, é possível montar uma cadeia de instâncias da classe Handler como se fosse uma lista simplesmente encadeada. 
 
A classe Handler é uma classe abstrata, portanto, não pode ser instanciada diretamente. Assim, para montar uma cadeia de objetos é necessário especializá-la em classes concretas, as quais estão representadas no diagrama da imagem, como as classes Pagamentos, Suporte, Sugestão e Vendas.

Outros materiais