Arquivo da tag: GRASP

RDD – Responsibility Driven Design e GRASP – General Responsibility Assignment Software Principles (2 de 2)

Olá pessoal!

Neste artigo veremos os demais padrões GRASP não abordados no anterior. São eles:

Controller – Determina que deve haver uma classe ou camada responsável por receber e tratar eventos da camada de interface com o usuário, delegando as ações para as camadas inferiores, de forma que ela funcione como intermediadora. O padrão também diz que pode ser criada uma controller para cada caso de uso. Ou seja, as funcionalidades providas pela interface, que dizem respeito a determinados casos de uso são delegadas para as controllers correspondentes. O objetivo com a controller é desacoplar (veja Low Coupling) a camada de interface com o usuário – que deve focar em apresentação, estilos, design, etc. – da implementação em si, desta forma aumentando a coesão (veja High Coesion), já que cada camada atenderá apenas a responsabilidades específicas e bem definidas.

Diagrama de classes: Exemplo padrão GRASP Controller

 

Pure Fabrication – Determina que assuntos não diretamente relacionados ao domínio da aplicação devem ser tratados por classes apartadas, denominadas "invenção pura". Ou seja, aquilo que para o domínio da aplicação não faça sentido é considerado e apartado como uma "invenção". Um exemplo desta situação é o encapsulamento de funcionalidades e particularidades para acesso a um banco de dados (componentes que encapsulam o driver de acesso a banco de dados, como por exemplo, o ADO .NET), em classes específicas que possam ser reutilizadas dentre as demais classes que precisam da mesma funcionalidade. Classes utilitárias e diversas funcionalidades providas por frameworks em geral são consideradas invenções puras. Este padrão ajuda no aumento da coesão (veja High Coesion), uma vez que define que as classes de domínio não contenham funcionalidades que vão além das suas reais responsabilidades. Desta forma, o reuso também é favorecido, já que as invenções puras são extraídas do domínio, de forma que possam ser reutilizadas.

Polymorphism – O polimorfismo prega que operações polimórficas sejam utilizadas ao invés de decisões. Para ilustrar este conceito nada melhor que um exemplo. Considere uma classe Pessoa, com os atributos Nome, NumeroCpf, NumeroCnpj e Tipo (Fisica ou Juridica). Neste modelo, todas as classes que precisem obter o documento da pessoa muito provavelmente implementarão um IF com base no atributo Tipo e considerarão ou o NumeroCpf ou o NumeroCnpj como o documento. Segundo o padrão, criar uma operação polimórfica significa criar uma interface Pessoa com o atributo Nome e uma operação ObterDocumento e duas classes que implementam esta interface: PessoaFisica (NumeroCpf) e PessoaJuridica (NumeroCnpj). Cada classe fará sua própria implementação da operação ObterDocumentDiagrama de classes: Exemplo padrão GRASP Polymorphismo, de forma que os demais utilizadores não tenham mais que se preocupar com a regra para obtenção do documento com base no tipo da pessoa. Um benefício desta abordagem é que caso seja necessário adicionar um terceiro tipo de pessoa (PessoaEstrangeira), o impacto seria menor para as classes dependentes, já que a operação polimórfica é a mesma (ObterDocumento).

Indirection – Determina que o sistema não conheça e que não esteja acoplado (veja Low Coupling) às implementações reais e especificas de determinados assuntos. Projetar para indireção pode envolver a criação de camadas que encapsulam determinadas funcionalidades ou que desacoplem outras camadas. O padrão Controller é um exemplo de indireção, já que com ele a user interface não depende diretamente das camadas inferiores (model, por exemplo), e vice-versa. Existem diversas formas de se aplicar a indireção. Além do MVC, outro exemplo de aplicação deste conceito é a Injeção de Dependência, onde as implementações dependem de interfaces que são realizadas em objetos concretos apenas em tempo de execução. Diversos design-patterns também se beneficiam deste conceito, por exemplo: Abstract Factory, Facade, Adapter, Strategy, Proxy, etc. Um exemplo simples e recorrente de indireção é o que o Visual Studio faz quando adicionamos uma referência para um serviço em um projeto. Por traz dos panos são criadas classes Proxy que encapsulam toda a complexidade envolvida no processo de chamar um serviço através do protocolo utilizado (SOAP, p. ex.), serializar e deserializar objetos de transferência, tratar seu retorno, etc. Todo o restante do sistema que utiliza o objeto Proxy criado se quer sabe o que está por traz dele, de forma que se o protocolo de comunicação for alterado, por exemplo, nada será afetado além do Proxy.

Protected Variations – A variação protegida é uma forma de indireção. A diferença é que neste caso o principal objetivo é proteger o sistema ou uma classe de variações previstas ou que tenham grandes possibilidades de ocorrer. Um exemplo deste tipo de situação é quando utilizamos componentes ou serviços de terceiros1 ou mesmo quando precisamos integrar com APIs de pacotes de aplicações. Em todas estas situações a ideia é proteger seu sistema ou sua classe da possibilidade de alteração na interface do componente, do serviço ou da API. As mesmas abordagens e padrões utilizados para a indireção também se aplicam à variação protegida. Conceitos e padrões de EAI (Enterprise Application Integration) e SOA (Service Oriented Architecture) também apoiam a indireção e a variação protegida de uma forma mais ampla.

RDD – Responsibility Driven Design e GRASP – General Responsibility Assignment Software Principles (1 de 2)

158382_4Olá pessoal

Neste post vamos conhecer alguns conceitos de programação orientada a objetos (POO) que nos ajudam a pensar em como estruturar projetos orientados a objetos. Ambos os conceitos abordados neste post estão descritos no livro “Utilizando UML e Padrões – Uma introdução à Análise e ao Projeto Orientado a Objetos e ao Desenvolvimento Iterativo” de Craig Larman – Livro muito bom que trata em detalhes de diversos assuntos bastante pertinentes no nosso dia-a-dia: processos de desenvolvimento iterativos, incrementais e ágeis, papéis e responsabilidades, artefatos, UML, conceitos de programação orientada a objetos, RDD, padrões GRASP, design-patterns (GOF) e outros.

Vamos começar então pelo RDD. Segundo o livro, RDD é uma metáfora geral que nos leva a raciocinar sobre projetos de software orientados a objetos, sob o ponto de vista de responsabilidades. Por responsabilidades, entende-se o que nossas classes de objetos ou mesmo componentes de software devem fazer e saber. Em um modelo de classes o fazer é realizado por métodos e o saber por atributos. Outro conceito envolvido no RDD é o de colaboração. Uma vez definidas as responsabilidades, há de se definir como seus objetos colaboração para que o objetivo seja atingido.

Já o GRASP define nove padrões ou princípios voltados à atribuição de responsabilidades, que apoiam o RDD. Analisando estes conceitos e os padrões GRASP, que veremos em detalhe mais a frente, podemos concluir que ambos apenas endereçam questões básicas de orientação a objetos. E o objetivo é justamente esse. Tanto RDD quanto GRASP servem como uma ferramenta para apoiar no domínio de conceitos básicos de programação orientada a objetos. E quando falamos de conceitos básicos, logo veremos que diversos tópicos abordados fundamentam outros padrões mais específicos, como por exemplo, MVC e os design-patterns do GOF. Isso é interessante, porque para entender estes padrões mais específicos temos que ter os conceitos básicos bem fundamentados. Outro ponto importante é que alguns padrões não se restringe apenas ao mundo de orientação a objetos. Alguns conceitos empregados nos padrões High Coesion, High Coupling, Indirection e Protected Variation são altamente pertinentes e recorrentes em arquiitetura de software. Vamos então aos padrões GRASP.

Diagrama de classes - Composição NotaFiscal - Item

Creator – Este padrão determina quem é responsável por criar quem. Uma das possibilidades a considerar, por exemplo, é que uma classe A deve ser responsável por criar uma classe B caso a classe A tenha uma relação de composição com a classe B. Outra possibilidade citada por este padrão é que a classe A pode ser responsável por criar a classe B caso ela tenha consigo informações suficientes para a inicialização da classe B ou caso use B de maneira muito próxima. Considere, por exemplo, uma relação entre uma classe NotaFiscal e Item, que usualmente denota uma relação de composição. Ou seja, uma nota fiscal é composta por itens de forma que não faça sentido sua existência sem eles. Neste exemplo, a responsabilidade por criar instâncias da classe Item seria da classe NotaFiscal, que poderia fazê-lo através de uma operação “IncluirItem”.

Information Expert – Determina a qual classe deve ser atribuída a responsabilidade de prover uma nova funcionalidade. O padrão diz que a classe que possuir mais informações a respeito da funcionalidade em questão deve ser a responsável por provê-la. Considerando o exemplo anterior (NotaFiscal – Item), caso precisássemos saber a quantidade de itens de uma nota ou o valor total do itens quem deveria ser a classe responsável? A classe NotaFiscal é a classe que tem mais informações sobre seus Itens, já que ela é composta por Itens, logo, as operações ObterQuantidaDeItens e ObterValorTotal é uma responsabilidade da classe NotaFiscal. Note que tanto este quanto o padrão Creator são bem básicos e definem conceitos bastante triviais. O que é importante observar é que ambos definem regras gerais que estão por traz do que fazemos por “intuição” no dia-a-dia.

High Coesion – A alta coesão diz respeito à criação de classes que tratem de assuntos focados e bem definidos. Uma classe não coesa é aquela que trata de diversos assuntos distintos, tornando-se difícil de entender, manter, testar, evoluir e reutilizar. A alta coesão também suporta o baixo acoplamento (veja Low Coupling).

Low Coupling – O baixo acoplamento diz respeito à redução de dependência entre classes de forma que uma necessidade de mudança tenha menor impacto nos objetos dependentes. Projetar para baixo acoplamento envolve a utilização de conceitos de alta coesão (veja High Coesion), pois, classes mais coesas são menos dependentes entre si. Imagine, por exemplo, uma classe que contenha implementações de diversos assuntos diferentes – não coesa. Todos os demais módulos do sistema que dependam dos assuntos contidos nesta classe dependerão de uma mesma classe, de forma que alterações em qualquer um dos assuntos abordados proporcionem maior risco para todos os demais. Projetar para baixo acoplamento também envolve utilização de conceitos de encapsulamento. Encapsular significa esconder do mundo externo particularidades, implementações e informações que não digam respeito diretamente ao negócio / domínio, ou que sejam sujeitas a mudanças. Uma vez que tais informações sejam escondidas atrás de propriedades ou operações, por exemplo, as demais classes não serão afetadas quando houver alterações – ou seja, terão baixo acoplamento. O baixo acoplamento também pode ser atingido através de técnicas utilizadas para indireção (veja Indirection) e variação protegida (veja Protected Variation).

Por enquanto é só. No próximo artigo devo trazer os demais padrões GRASP: Controller, Pure Fabrication, Polymorphism, Indirection e Protected Variation. Até lá.