Prévia do material em texto
LUCIANO GASPAR ELISAMARA DE OLIVEIRA Professor autor/conteudista Atualizado e revisado por É vedada, terminantemente, a cópia do material didático sob qualquer forma, o seu fornecimento para fotocópia ou gravação, para alunos ou terceiros, bem como o seu fornecimento para divulgação em locais públicos, telessalas ou qualquer outra forma de divulgação pública, sob pena de responsabilização civil e criminal. SUMÁRIO Apresentação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1 . Fundamentos de arquitetura de software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.1. Definição de arquitetura de software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .6 1.2. Modelagem de aplicações de softwares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .7 1.3. Tarefas acidentais e essenciais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .9 1.4. Aspectos essenciais na produção de software. . . . . . . . . . . . . . . . . . . . . . . . . . . . .11 1.5. Princípios SOLID. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .17 1.5.1. Princípio de Responsabilidade Única . . . . . . . . . . . . . . . . . . . . . 19 1.5.2. Princípio Aberto-Fechado . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 1.5.3. Princípio de Substituição de Liskov . . . . . . . . . . . . . . . . . . . . . . 22 1.5.4. Princípio de Segregação de Interface. . . . . . . . . . . . . . . . . . . . . 23 1.5.5. Princípio de Inversão de Dependência . . . . . . . . . . . . . . . . . . . . 23 2 . Padrões de arquitetura de software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 2.1. Definição de padrão arquitetural . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 2.2. Padrão de arquitetura em camadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Padrão de arquitetura Pipes and filters ou Pipeline . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 2.4. Padrão de arquitetura Blackboard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 2.5. Model-View-Controller (MVC) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 Padrão de arquitetura Microkernel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 2.7. Padrão de arquitetura Reflection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 3 . Design patterns – Padrões de criação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 3.1. Conceitos de padrões de projeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 3.2. Definição de padrões de criação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .52 3.3. Padrão Abstract Factory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 3.4. Padrão Builder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 3.5. Padrão Factory Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 3.6. Padrão Prototype . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 3.7. Padrão Singleton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 4 . Design patterns – Padrões estruturais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 4.1. Definição de padrões estruturais. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 4.2. Padrão Adapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 4.3. Padrão Bridge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 4.4. Padrão Decorator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 4.5. Padrão Façade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 4.6. Padrão Proxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 5 . Design patterns – Padrões comportamentais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 5.1. Definição de padrões comportamentais. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .75 5.2. Padrão Chain of Responsability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 5.3. Padrão Mediator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 5.4. Padrão Observer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 5.5. Padrão Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 5.6. Padrão Visitor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 Considerações finais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 Glossário de Siglas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 Glossário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 Bibliografia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 Pág. 5 de 91 APRESENTAÇÃO Os sistemas de informação são provedores de solução para as mais diversas áreas do conhecimento e impulsionam a indústria, o comércio, os serviços e, em especial, os segmentos de mercado dependentes de tecnologia ou mesmo novos negócios que surgem apoiados em software. Segundo Laudon (2010), as empresas apoiam seus processos de gestão nas tecnologias, com o objetivo de gerenciar suas operações, desenvolver novos produtose serviços, estreitar o relacionamento com clientes e fornecedores, auxiliar na tomada de decisões e obter vantagem competitiva. A importância crescente do software, aliado ao maior conhecimento (desde 1967) sobre como construí-lo, resultou na necessidade de uma formação mais ampla para o programador. Este deve se preocupar em aplicar técnicas e métodos especializados para projetar, construir, testar e manter os produtos de software. Uma parte considerável destes conhecimentos pertence à área de engenharia de software. Após a “crise do software”, uma longa série de inovações passou a ser adotada nas práticas de desenvolvimento, incluindo modularidade, programação estruturada, diagramas de fluxo de dados, proteção de informações, information hiding, abstração de dados, reutilização de software, programação visual, programação orientada a objeto (POO), padrões de projetos e ferramentas CASE (Computer-aided Software Engineering). Porém, tudo isso trouxe ganhos modestos e não transformou a engenharia de software em algo sistemático, disciplinado e quantificável para o desenvolvimento, a operação e a manutenção do software. A engenharia de software possui peculiaridades que talvez nenhuma outra área do conhecimento tenha. Isso nos faz refletir sobre quais competências um profissional que atue em desenvolvimento de sistemas de software tenha que adquirir. Assim, prezado aluno, este material foi elaborado para chamar sua atenção e proporcionar uma reflexão sobre os principais aspectos que têm afetado diretamente a produção do software. Além disso, vamos apontar os princípios ligados à arquitetura e aos padrões de projeto que podem ser seguidos, de forma a trazer um diferencial na carreira do desenvolvedor de soluções em software, pois o conhecimento das dificuldades inerentes a esta área tornará mais fácil o trabalho quando elas surgirem. Luciano Gaspar Elisamara de Oliveira Pág. 6 de 91 1. FUNDAMENTOS DE ARQUITETURA DE SOFTWARE Primeiramente, abordaremos conceitos iniciais sobre o projeto de arquitetura de software e as consequências de um software não arquitetado. Será discutido o papel do arquiteto de software, assim como as barreiras e os princípios de modelagem que devem ser levados em consideração na concepção de uma arquitetura. 1.1. Definição de arquitetura de software Para entendermos a necessidade de se arquitetar um software, alguns conceitos devem ser apresentados, bem como os desafios com os quais um profissional desta área deve lidar. Historicamente, os fracassos de projetos de softwares são significativos. De acordo com Hastie e Wojewoda (2015), o Chaos Report da Gartner de 2015 mostra que apenas 29% dos projetos foram bem sucedidos, 52% chegaram ao final ainda necessitando de ajustes e 19% falharam. Os fatores críticos de sucesso estão relacionados à natureza complexa e intangível do software. O software é visto pelas áreas de negócios como algo flexível e adaptável, porém sabemos que um software não arquitetado pode dificultar sua manutenção, escalabilidade e reúso. Desta forma, desenvolver soluções arquitetadas é uma prática que tornará nosso trabalho, a longo prazo, menos reativo e estressante. Mas o que é arquitetura de software? IMPORTANTE Bass et al. (2003) definem arquitetura de software como a estrutura do sistema composta por elementos de software (componentes, conectores ou dados), com propriedades externamente visíveis destes elementos e os relacionamentos entre eles. Uma arquitetura registra informações sobre como os elementos de software se relacionam uns com os outros, ocultando ou explicitando elementos do software que devem ou não existir em diferentes níveis de abstração. As figuras 1 e 2 ilustram um projeto arquitetado e um não arquitetado. Reflita sobre as principais características entre eles. Pág. 7 de 91 Figura 1 – Bairro planejado Fonte: Struvictory/Shutterstock Figura 2 – Crescimento desordenado Fonte: Donatas Dabravolskas/Shutterstock REFLITA Os softwares que estamos desenvolvendo são mais parecidos com a figura 1 ou com a figura 2? Nossas aplicações possuem um padrão arquitetural? São concebidos para aceitar novas funcionalidades? Permitem o reúso e a escalabilidade? Aqueles que possuem vivência na área de desenvolvimento de software sabem que, muitas vezes, uma mudança ou um novo requisito tem impacto estrutural e demanda grande esforço de desenvolvimento e, se realizado sem planejamento, desencadeia efeitos colaterais em todo o software. 1.2. Modelagem de aplicações de softwares Como você já deve ter estudado, um aplicativo de software possui um ciclo de vida. Ao longo deste ciclo, muitas mudanças são realizadas e geram impactos no código escrito nas primeiras versões do software. O software é afetado por um fenômeno de degradação que impacta sua confiabilidade, estrutura e sua consistência. Sua documentação fica desatualizada, dificultando sua manutenção e tornando-a onerosa. Sabemos que um processo de desenvolvimento definido que favoreça a obtenção e a validação dos requisitos pode minimizar esses impactos, porém o fato de o software ser visto como algo flexível torna a definição de uma arquitetura um elemento fundamental para o desenvolvimento de software em larga escala. Pág. 8 de 91 Se você já desenvolveu algum software ou até mesmo uma planilha eletrônica com várias referências a fórmulas em diferentes arquivos, já percebeu que existe um limite para a capacidade humana para compreender, ao longo do tempo, aquilo que foi produzido, tornando esta uma atividade complexa. O software possui uma natureza complexa crescente, seja pelo número de linhas de código ou pela sua intangibilidade. Logo, a criação de modelos torna-se indispensável para compreensão do que está sendo desenvolvido. O uso de modelos apoia o entendimento e a definição da arquitetura do software, assim como a identificação das regiões críticas que podem sofrer degradação ao longo do tempo. No processo de criação de modelos, muitos problemas podem ser antecipados e decisões são tomadas a fim de minimizar seu impacto ao longo do tempo. IMPORTANTE Blaha e Rumbaugh (2006, p. 17) definem modelo como uma abstração de algo que facilita seu entendimento antes de construí-lo. Abstração é um processo mental básico que permite lidar com a complexidade, omitindo detalhes não essenciais. A abstração permite a simplificação de algo complexo, como no exemplo na figura 3. Figura 3 – Abstração de uma casa Fonte: Melissa King/Shutterstock.com Pág. 9 de 91 No processo de produção do software, modelos são criados para descrever a estrutura e o comportamento de um software para posterior implementação. Estas descrições de modelos guiam a construção e mantêm registros das decisões tomadas na sua concepção. Para os criadores da Unified Modeling Language (UML), Booch, Rumbaugh e Jacobson, nenhum modelo único é suficiente. Qualquer sistema não trivial será mais bem investigado por meio de um conjunto de modelos quase independentes com vários pontos de vista. Um ou mais modelos podem servir de inspiração para dar origem a uma arquitetura que sustente as necessidades do que está sendo modelado. IMPORTANTE De acordo com Zachman (1997), Arquitetura é o conjunto de artefatos ou um descritivo relevante de um objeto, de forma que este possa ser construído de acordo com os requisitos (de qualidade), bem como mantido ao longo da sua vida útil. Neste sentido, modelar um sistema significa determinar e representar um conjunto de informações arquitetadas sob diferentes vistas que servirão de guia de referência para a produção de algo concreto (código-fonte). Cada área de conhecimento adota tipos específicos de modelos. Na engenharia de software contemporânea, segundo Booch, Rumbaugh e Jacobson (2006), adota-se uma perspectiva orientada a objetos, onde a UML é empregada para visualização, especificação, construção e documentação de artefatos que orientamo desenvolvimento do software. Para entender a importância de controlar esta complexidade, vamos analisar no próximo tópico o trabalho de Brooks (1987), que faz uma classificação dos aspectos que estão sob nosso controle e aqueles que fogem dele, impactando o cronograma, o custo e a equipe de projeto do software. 1.3. Tarefas acidentais e essenciais Independentemente do processo de desenvolvimento de software adotado, um conjunto de tarefas é organizado e realizado. Tais tarefas são classificadas por Brooks (1987) como tarefas acidentais e essenciais: • As tarefas acidentais estão relacionadas à implementação do software e as principais preocupações estão ligadas à sintaxe das linguagens, aos limites de hardware, aos ambientes e Pág. 10 de 91 ferramentas de desenvolvimento e a demais aspectos técnicos. Estes aspectos são facilmente superados com treinamentos, leituras ou por meio de consulta a um profissional mais experiente. • As tarefas essenciais estão relacionadas ao processo mental de criação de um conjunto de conceitos que se interligam. Tanto as tarefas acidentais quanto as essenciais criam barreiras para o desenvolvimento de software, porém grande parte das barreiras acidentais foi transposta na última década e as principais falhas de projetos estão relacionadas às atividades essenciais que criam barreiras que dificilmente são domináveis. Figura 4 – Atividades essenciais e seu peso Fonte: iQoncept/Shutterstock De forma análoga, e neste contexto, pode-se comparar o processo de desenvolvimento de software ao processo de criação de um texto, em que as tarefas acidentais estão relacionadas ao domínio de uma ferramenta de edição de textos, a sintaxe e a semântica da língua. Tais tarefas podem criar barreiras para a produção do texto, mas serão superadas com algum nível de estudo ou apoio de terceiros, porém não garantem que o texto escrito atenderá critérios de qualidade. Logo, o domínio de tarefas acidentais não garante a qualidade do que está sendo produzido. A qualidade será definida pela forma peculiar de criação e organização das ideias atuando sobre a construção mental e essencial que será descritos de forma textual. Pág. 11 de 91 1.4. Aspectos essenciais na produção de software Brooks (1987) elenca quatro aspectos essenciais que impactam na produção do software. São eles: complexidade, conformidade, flexibilidade e intangibilidade. Estes aspectos são descritos a seguir. Complexidade Entidades de software possuem uma natureza complexa. Assim, aumentar sua escala não é meramente repetir os mesmos elementos em tamanho maior. É necessário um aumento do número de elementos diferentes, amplificando a complexidade do todo de forma não linear. Queremos dizer que existe uma grande diferença entre fazer um software com poucas linhas de código e desenvolver um software com um número maior de requisitos. Para um software mais robusto, não basta aumentar as linhas de código. É necessário incluir elementos de software para facilitar o entendimento das linhas já produzidas, a manutenção, a inclusão de novas funcionalidades, o aumento da equipe, a padronização e a inclusão de interfaces, entre outras demandas exigidas por um software de maior escala. Imagine um artesão trabalhando na produção de um vaso de barro. Você, de passagem em turismo pelo local, adorou aquele vaso. Em função disso, solicitou ao artesão que produzisse 1.000 unidades, pois presentearia seus familiares e venderia o restante dos vasos na sua cidade de origem. Figura 5 – Artesão trabalhando na produção de um vaso Fonte: Thirteen/Shutterstock Pág. 12 de 91 REFLITA O processo e as ferramentas de produção adotados pelo artesão seriam suficientes para atender sua encomenda? Bastaria apenas aumentar em quantidade a matéria-prima do artesão? Contratar mais artesões garantiria a beleza do vaso apreciada por você? O processo para produzir uma unidade é o mesmo para produzir 1.000 unidades? Acredito que chegará facilmente à conclusão que novos elementos deverão ser inseridos no processo de produção do vaso. Talvez o uso de formas para garantir o padrão, a contratação de novos artesões, uma sequência ordenada de tarefas, a criação de modelos que serão submetidos a testes, entre outras ações. AGORA, REFLITA: O que diferencia a produção de um software de 1.000 linhas de código com um de 15.000 linhas? Será só a quantidade de linhas? Softwares com 1.000 linhas de código, no pior caso, podem ser refeitos gastando apenas mais alguns dias de trabalho; entretanto, reescrever um sistema mais complexo levaria meses ou anos. Portanto, cuidar da arquitetura no início reduz os problemas futuros. A figura 6 exibe os impactos da complexidade na produção do software. Da complexidade, vem a dificuldade de entender e se comunicar com os membros da equipe de desenvolvimento, levando à deficiência do produto, que aumenta os custos e afeta o cronograma e a confiabilidade. Da complexidade da estrutura, surge também a dificuldade de entender o comportamento e ampliar os programas sem criar efeitos colaterais, tornando árduo o gerenciamento destes problemas. Figura 6 – Impactos da complexidade no desenvolvimento de software 1 complexidade 2 Comunicação 7 Falta de Con�abilidade 6 Di�culdade de entender 5 Atraso de Crono- grama 4 Aumento dos custos 3 De�ciência do Produto Fonte: Elaborado pelo autor. Pág. 13 de 91 Provavelmente, muitos dos programadores já passaram por situações em que não conseguiram ou demoraram para entender um código, seja ele gerado por terceiros ou até mesmo um código próprio. Segundo Brooks (1987), a complexidade do código é crescente e existe um limite que o cérebro humano consegue gerenciar, impactando na quantidade de novas linhas de código que podem ser acrescentadas a um programa ao longo do tempo. A figura 7 mostra a relação produtividade no desenvolvimento versus tempo. À medida que o software é implementado, ou seja, linhas de código são inseridas, sua complexidade cresce e afeta a produtividade ao longo do tempo. Figura 7 – Tempo versus produtividade 100 80 60 40 20 0 Pr od ut iv id ad e Tempo Fonte: Martin (2009). Conformidade e flexibilidade Outras áreas do conhecimento também lidam com a complexidade crescente, mas como o software não está suscetível às leis naturais, é visto como algo flexível e de fácil adequação, ao menos se comparado à Física e outras ciências. Brooks (1997) afirma que, em decorrência da sua complexidade, a versão inicial de um software muitas vezes não atende aos requisitos na sua plenitude, necessitando de constantes mudanças, de tal modo que todo software de sucesso acaba sendo modificado. Assim, um sistema de software deve ser concebido para mudar. Pág. 14 de 91 Caso você já atue na área de desenvolvimento de software, deve ter percebido que a mudança dos requisitos é algo constante. Isso não significa que as mudanças são correções de erros, mas podem decorrer de novas funcionalidades para atender às necessidades de negócios. Vale dizer que a TI deve impulsionar os negócios e não ser um gargalo para novas oportunidades. Logo, precisamos desenvolver softwares nos quais uma mudança não afete toda a estrutura já desenvolvida. Nosso código deve “aceitar” mudanças com certa flexibilidade, comunicar claramente suas responsabilidades, facilitar a manutenção e proporcionar o reúso. Esse é o grande desafio! Intangibilidade Softwares são intangíveis, portanto, é difícil criar uma representação visual objetiva e comum para eles. Ao tentar diagramar uma estrutura de software, descobre-se que ela não é apenas um, mas sim vários diagramas superpostos uns aos outros, sem hierarquias ou relações diretas entre seus elementos, forçando cortes que eliminam conexões nas diferentes vistas do modelo. Para Brooks (1987), esses cortes prejudicam não só o processo de projeto do software, mas também a comunicação entre osmembros da equipe. A UML é uma poderosa ferramenta capaz de tangibilizar as descrições de implementação do arquiteto de software. Figura 8 – UML Fonte: dizain/Shutterstock Pág. 15 de 91 Um arquiteto de software deve apoiar-se em descrições de modelos para comunicar suas decisões de implementação. Um diagrama UML, analogamente, pode ser comparado com uma planta hidráulica ou elétrica que um engenheiro civil ou eletricista utiliza para tangibilizar um modelo mental e comunicar seu projeto. Portanto, a descrição de modelos de software apoiados em diagramas é uma ferramenta essencial para um arquiteto de software e um dos elementos que o difere do programador. ACONTECEU UML: um breve histórico A UML surgiu da união de três metodologias de modelagem: o método do americano Grady Booch, o método OMT (Object Modeling Technique), do sueco Ivar Jacobson, e o método OOSE (Object-oriented Software Engineering) do americano James Rumbaugh. Estas eram, até meados da década de 1990, as três metodologias de modelagem orientada a objeto mais populares. A união dessas tecnologias contou com o apoio da Rational Software, que incentivou e financiou a união das três metodologias. O esforço inicial do projeto começou com a união do método de Booch com o de Jacobson, o que resultou no lançamento do método unificado no final de 1995. Logo em seguida, Rumbaugh juntou- se a Booch e Jacobson na Rational Software, e seu método OOSE começou a ser incorporado à nova metodologia. O trabalho de Booch, Jacobson e Rumbaugh, conhecidos popularmente como “os três amigos”, resultou no lançamento, em 1996, da primeira versão da UML propriamente dita. Assim que a primeira versão foi lançada, diversas grandes empresas atuantes na área de software passaram a contribuir com o projeto, fornecendo sugestões para melhorar e ampliar a linguagem. Finalmente, a UML foi adotada pela OMG (Object Management Group), em 1997, como a linguagem padrão de modelagem. Fonte: Guedes (2014, p. 15-16). Quadro 1 – Classes, objetos e métodos O que é orientação a objetos (OO) O termo orientado a objetos (OO) significa que organizamos o software como uma coleção de objetos distintos, que incorporam estruturas de dados e comportamentos. Isso é diferente das técnicas de programação procedural, nas quais as estruturas de dados e o comportamento estão pouco conectados. A programação OO insere novos conceitos no desenvolvimento de software como, por exemplo, classes, objetos e métodos. Pág. 16 de 91 Classes, objetos e métodos Classes são representações de um conjunto de objetos com características particulares. A classe é definidora do comportamento dos objetos por meio de seus métodos e dos estados que o objeto é capaz de manter por meio de seus atributos. Numa classe de nome Motor, por exemplo, a propriedade Ligar pode ser definida como um de seus atributos. Um objeto é uma representação, por meio de uma classe, de algo que pode ser instanciado e utilizado para interagir com outros objetos em tempo de execução. O objeto pode armazenar um estado por meio de seus atributos e reagir a mensagens enviadas a ele por outros objetos, assim como pode se relacionar e enviar mensagens a outros objetos. Métodos são as definições de possibilidades de execução de ações de objetos. Esta ação que é definida no método só ocorre quando for invocada por outro objeto. Um objeto definido por meio de uma classe pode conter diversos métodos. Ao ser criada uma classe de um objeto Transporte, por exemplo, a instância Carro pode criar os métodos Abastecer, Andar, Estacionar etc. Fonte: Blaha e Rumbaugh (2006, p. 1). Polimorfismo e herança em OO Polimorfismo significa que a mesma operação pode atuar de modos diversos em classes diferentes. Uma operação é uma ação ou transformação que um objeto executa ou a que ele está sujeito. Right- justify (justificar à direita), display (exibir) e move (mover) são exemplos de operações. A operação move (mover), por exemplo, pode atuar de modo diferente nas classes Janela e Peça de Xadrez. Uma implementação específica de uma operação por uma determinada classe é chamada de método. Quando uma operação baseada em objetos é polimórfica, pode haver mais de um método para a sua implementação. No mundo real, uma operação é simplesmente uma abstração de um comportamento análogo entre diferentes tipos de objetos. Cada objeto “sabe” como executar suas próprias operações. Entretanto, uma linguagem de programação baseada em objetos seleciona automaticamente o método correto para implementar uma operação com base no nome da operação e na classe do objeto que esteja sendo operado. O usuário de uma operação não precisa saber quantos métodos existem para implementar uma determinada operação polimórfica. Novas classes podem ser adicionadas sem que se modifique o código existente, pois são fornecidos métodos para cada operação aplicável nas novas classes. Herança é o compartilhamento de atributos e operações entre classes com base num relacionamento hierárquico. Uma classe pode ser definida de forma abrangente e depois ser refinada em sucessivas subclasses mais definidas. Cada subclasse incorpora ou herda todas as propriedades da sua superclasse e acrescenta suas próprias e exclusivas características. As propriedades da superclasse não precisam ser repetidas em cada subclasse. Por exemplo: Janela Rolante e Janela Fixa são subclasses da superclasse Janela. Estas duas subclasses herdam as propriedades de Janela, como uma região visível na tela. Janela Rolante acrescenta uma barra de rolagem e limites de rolagem. A capacidade de identificar propriedades comuns a várias classes de uma superclasse comum e de fazê-las herdarem as propriedades da superclasse pode reduzir substancialmente as repetições nos projetos e programas, sendo uma das principais vantagens dos sistemas baseados em objetos. Fonte: Blaha e Rumbaugh (2006, p 2). Pág. 17 de 91 1.5. Princípios SOLID Em 1995, Robert C. Martin iniciou uma discussão sobre um conjunto de princípios de modelagem de software orientado a objetos. Estes princípios ajudavam a lidar com os problemas oriundos da natureza do software discutidos até aquele momento. Booch, Rumbaugh e Jacobson (2005, p. 135) definem que, em OO, “responsabilidade é um contrato ou obrigação de um tipo ou classe”. Os autores afirmam que há dois tipos de responsabilidades dos objetos: • De conhecimento (knowing): sobre dados privativos e encapsulados; sobre objetos relacionados; sobre coisas que pode calcular ou derivar. • De realização (doing): em fazer algo em si mesmo; iniciar uma ação em outro objeto; controlar e coordenar atividades em outros objetos. Responsabilidades são atribuídas aos objetos durante o projeto do software OO. A tradução de responsabilidades em classes e métodos depende da granularidade da responsabilidade. Os métodos são implementados para cumprir responsabilidades. Uma responsabilidade pode ser cumprida por um único método ou por uma coleção de métodos trabalhando em conjunto. Responsabilidades do tipo knowing geralmente são inferidas a partir do modelo conceitual (são os atributos e os relacionamentos). Figura 9 – Object-oriented Programming (OOP) Fonte: Bobicova Valeria/Shutterstock Pág. 18 de 91 Um dos pontos que levam à degradação do código ao longo do tempo é o alto acoplamento entre os objetos (que é o foco), o qual deve ser gerenciado, de modo a reduzir a dependência entre eles. No paradigma orientado a objetos, uma das principais preocupações é o acoplamento . Um acoplamento pode ocorrer de diferentes formas: • quando uma classe A possui um atributo que referencia o objeto B ou a própria classe B; • quando um objeto A chama um método de um objeto B; • quando um objeto A tem um método que referencia um objeto B ou a própria classe B, tipicamente caracterizado por um parâmetro ou variável local do tipo B ou um objeto retornado de uma mensagem sendo uma instância de B. Tais formas de acoplamento podemser mitigadas com mudanças no código-fonte. Uma boa prática no desenvolvimento de software OO é criar estruturas com baixo acoplamento entre objetos, para que a atribuição de responsabilidades seja feita de forma que sua alocação não aumente o acoplamento de um objeto com outros. Ainda dentro de um projeto OO, existe outro conceito muito importante: a coesão. Ela é uma medida de quão fortemente estão relacionadas e focadas as responsabilidades de um dado elemento. Um elemento que esteja altamente especializado e que não realize uma grande quantidade de tarefas é considerado com alta coesão, ou seja, possui um alto grau de coesão. Assim, uma classe com baixa coesão geralmente executa muitas funções pouco relacionadas entre si, realizando um trabalho excessivo. Classes deste tipo são altamente indesejáveis em um projeto, pois acumulam os seguintes problemas: • são de difícil compreensão; • não conseguem ser reutilizadas; • são de difícil manutenção; • são constantemente afetadas por qualquer modificação no projeto. IMPORTANTE Baixo acoplamento (low coupling) e alta coesão (high cohesion) são conceitos relevantes que devem ser considerados e preservados sempre durante os processos de tomada de decisão em um projeto orientado a objetos. Estão intimamente ligados ao desenvolvimento de softwares que priorizam uma arquitetura que favorece a manutenção e a construção de objetos reusáveis. Pág. 19 de 91 O gerenciamento de dependência entre objetos é um problema que certamente já foi enfrentado por quem desenvolve ou desenvolveu um aplicativo OO. Não se preocupar com o gerenciamento de dependência leva a um código confuso, difícil de mudar e não reutilizável. Os princípios a seguir concentram-se na gestão de dependências dos objetos de um sistema OO e são conhecidos como SOLID. São estes os princípios SOLID: • S – Single Responsability Principle (Princípio de Responsabilidade Única) • O – Open/Closed Principle (Princípio Aberto-Fechado) • L – Liskov Substitution Principle (Princípio de Substituição de Liskov) • I – Interface Segregation Principle (Princípio de Segregação de Interface) • D – Dependency Inversion Principle (Princípio da Inversão de Dependência) Figura 10 – Princípios SOLID Princípios S.O.L.I.D. Fonte: <https://www.quora.com/How-do-I-write-PHP-code-that-easy-to-maintain-and-upgrade>. Cada um destes princípios será detalhado a seguir. 1.5.1. Princípio de Responsabilidade Única O Princípio de Responsabilidade Única, também conhecido como Single Responsability Principle (SRP) estabelece que uma classe deve ter um e somente um motivo para mudar. Se uma classe é responsável por efetuar várias ações dentro de um determinado contexto, ela está ferindo este princípio. Vamos analisar o exemplo: Pág. 20 de 91 A classe Retângulo, da figura 11 possui os métodos Área, que calcula a área do retângulo, e Desenhar, que desenha o retângulo. Essa classe fere o princípio SRP, pois se a forma de cálculo for alterada ou uma interação com a interface gráfica tiver que ser alterada, ambas afetam a classe Retângulo. Ou seja, as linhas de código escritas nesta classe possuem duas formas de serem impactadas. Figura 11 – Classe Retângulo Retângulo + Área () + Desenhar () Fonte: Elaborado pelo autor. Para atender o princípio SRP, devemos separar as responsabilidades em duas classes diferentes, sendo uma para calcular a área e outra para desenhar o retângulo. Neste caso, teremos as classes RetânguloDes e RetânguloCal, cada uma com seus métodos, conforme mostra a figura 12. Figura 12 – Classes para cálculo da área e desenho de retângulos RetânguloDes + Desenhar () RetânguloCal + Area() Fonte: Elaborado pelo autor. 1.5.2. Princípio Aberto-Fechado O Princípio Aberto-Fechado, ou simplesmente Open/Closed Principle (OCP), estabelece que uma classe deve estar “aberta” para extensão, porém fechada para modificação, ou seja, devemos Pág. 21 de 91 organizar as classes para possibilitar o crescimento, porém sem alterar o código das classes existentes. Vejamos o exemplo da figura 13. Figura 13 – Classe Pagamento + Parcelado () + À Vista () + Débito () + Crédito () Pagamento Fonte: Elaborado pelo autor. A classe Pagamento possui os métodos que têm as instruções para pagamentos dos tipos Parcelado, À Vista, Débito e Crédito. Como você implementaria isso? Certamente, em determinado trecho de código, seria utilizada uma instrução IF ou CASE e, de acordo com a forma de pagamento selecionada pelo usuário, seria chamado um método específico. Caso você venha desenvolvendo softwares desta forma, lembre-se que o princípio OCP está sendo ferido. O resultado em não respeitar este princípio é que, para cada nova forma de pagamento, o código da classe Pagamento deverá ser alterado com a inclusão do novo método de pagamento e a inclusão de mais um parâmetro na instrução IF ou CASE. Para atender o princípio OCP, é necessário separar as formas de pagamento em subclasses. Desta forma, uma nova forma de pagamento não afetaria a classe Pagamento, ou seja, o código estaria fechado para alteração e aberto para extensão. Pág. 22 de 91 A figura 14 ilustra as classes, simplificadamente, para atender o OCP, neste exemplo. Figura 14 – Classes e subclasses para formas de pagamento Pagamento Parcelado À Vista Débito Crédito Fonte: Elaborado pelo autor. Aplicando o Princípio Aberto-Fechado, os problemas apontados são evitados, porque a classe Pagamento não precisa mais ser alterada quando uma nova forma de pagamento for inserida. Neste caso, deve ser feita a inclusão de uma subclasse, com a nova forma de pagamento e os métodos específicos deste tipo de pagamento. Desta forma, as classes serão mais simples se não estiverem sobrecarregadas de atribuições. 1.5.3. Princípio de Substituição de Liskov O Princípio de Substituição de Liskov, ou simplesmente Liskov Substitution Principle (LSP), estabelece que as classes derivadas devem ser substituídas por suas classes base.Se utilizarmos um objeto e, com o uso de polimorfismo, manipulá-lo como sendo do seu tipo base, ele deve funcionar e se comportar da mesma forma como se comportaria se realmente fosse daquele tipo. Retomando o exemplo ilustrado na figura 14 temos uma classe Pagamento (base) e subclasses derivadas (Parcelado, À Vista, Débito e Crédito). Respeitar o princípio LSP significa dizer que posso usar qualquer classe derivada como se fosse a classe base Pagamento, sem alterar o comportamento da classe base. Pág. 23 de 91 1.5.4. Princípio de Segregação de Interface O Princípio de Segregação de Interface ou Interface Segregation Principle (ISP) estabelece que interfaces sejam de “fina granularidade”, para que sejam específicas para quem vai utilizá-las. Uma interface não deve ter inúmeras funcionalidades. Deve ser a mais “enxuta” possível, seguindo o princípio SRP, porém voltado para as interfaces. Figura 15 – Princípio de Segregação de Interface Fonte: <https://lostechies.com/derickbailey/2009/02/11/solid-development-principles-in-motivational-pictures/>. 1.5.5. Princípio de Inversão de Dependência Com o objetivo de reduzir o acoplamento entre objetos, o Princípio de Inversão de Dependência ou Dependency Inversion Principle (DIP) estabelece que sempre devemos depender de classes abstratas e nunca de classes concretas. Se um objeto depender de classes concretas, qualquer alteração que ocorrer nesta classe afeta todas que dependerem dela. Observe a figura 16, elaborada com o software BlueJ (www.bluej.org), que mostra quatro classes distintas, cuja relação ocorre na forma de classes concretas. Pág. 24 de 91 Figura 16 – Relação entre classes Canvas Circle Square Triangle Fonte: Elaborado pelo autor. A figura 17 representa, por meio de marcações hachuradas, as classes que são afetadas quando a classe Canvas é alterada. Observe que uma alteração em Canvas afeta todas as demais classes relacionadas a ela. Figura 17 – Classes afetadasapós alteração em Canvas Canvas Circle Square Triangle Fonte: Elaborado pelo autor. Pág. 25 de 91 A figura 18 representa uma implementação em que existe uma relação entre as classes, porém todas dependem da classe abstrata Figure. Observe que esta classe impede a propagação da alteração realizada em Canvas. Figura 18 – Diagrama com classe abstrata Canvas Circle Square Triangle <<abstract>> Figure Fonte: Elaborado pelo autor. SAIBA MAIS Caro aluno, veja mais exemplos de códigos com aplicação de princípios SOLID em Marabesi (2016), disponível em: https://marabesi.com/oop/2016/04/12/s-o-l-i-d.html > e Oloruntoba (2015), disponível em: https://scotch.io/bar-talk/s-o-l-i-d-the-first-five-principles-of-object-oriented-design >. Pág. 26 de 91 2. PADRÕES DE ARQUITETURA DE SOFTWARE Neste capítulo, descreveremos os padrões arquiteturais. Alguns exemplos de aplicação serão apresentados, bem como os benefícios de sua utilização em diferentes casos. 2.1. Definição de padrão arquitetural Um padrão arquitetural determina a estrutura de um projeto de software. Portanto, trata-se de um conceito amplo que aborda questões como limitação de hardware, alta disponibilidade de uso e risco de negócio (BUSCHMANN et al., 2001). Como sabemos, o desenvolvimento de algoritmos visa resolver problemas com o auxílio do computador. Já houve uma época em que os programadores tinham muita dificuldade com a escrita e a compilação do código. No entanto, esta dificuldade já foi superada com a ajuda de ferramentas que auxiliam o programador na sintaxe e semântica das linguagens de programação. Temos agora um problema de segunda ordem que está relacionado a como organizar as linhas de código. Muitas vezes, sabemos quais linhas de código atenderão um determinado requisito de negócio, porém ainda precisamos saber qual a melhor organização das linhas de código que evitará a degradação da aplicação. Figura 19 – Planta de uma casa Fonte: Franck Boston/Shutterstock O uso de padrões arquiteturais pode ser um dos direcionadores desta organização. Um único padrão pode auxiliar na solução das necessidades ligadas à organização do código, contudo, é possível usar uma combinação de padrões. Vale lembrar que o conceito de padrões arquiteturais diferencia-se de padrões de projetos e ambos são assuntos tratados neste material. Pág. 27 de 91 IMPORTANTE Arquitetura de software, segundo Booch, Rumbaugh e Jacobson (2005, p. 34) é o “conjunto de decisões significativas” acerca dos seguintes itens: • a organização do sistema de software; • a seleção dos elementos estruturais e suas interfaces, que compõem o sistema; • seu comportamento, conforme especificado nas colaborações entre esses elementos; • a composição desses elementos estruturais e comportamentais em subsistemas progressivamente maiores; • o estilo de arquitetura que orienta a organização: os elementos estáticos e dinâmicos e suas respectivas interfaces, colaborações e composição. De uma maneira mais simplificada, Pressman (2011, p. 230) define arquitetura de software como a “estrutura do sistema que abrange os componentes de software, as propriedades externamente visíveis destes componentes e as relações entre eles”. O autor ainda completa que a arquitetura [...] não é o sistema operacional, mas sim uma representação que nos permite (1) analisar a efetividade do projeto no atendimento aos requisitos declarados, (2) considerar alternativas de arquitetura em um estágio quando realizar mudanças de projeto ainda é relativamente fácil e (3) reduzir os riscos associados à construção do software. As seções a seguir descrevem os principais padrões arquiteturais, suas vantagens e desvantagens. Vale lembrar que existem muitos outros padrões possíveis, simples ou compostos, mas vamos nos ater aos mais utilizados nesta abordagem. 2.2. Padrão de arquitetura em camadas De acordo com Buschmann et al. (2001), camadas são grupos de componentes reutilizáveis, similares e inter-relacionados, responsáveis por um nível de abstração particular, ou seja, as camadas executam tarefas específicas da aplicação consumindo e fornecendo recursos entre si. Objetivo Este padrão tem por objetivo decompor a aplicação em módulos reutilizáveis, organizados por funcionalidades específicas, como por exemplo, acesso a dados, lógica de negócio, construção da interface etc. Contexto O padrão de arquitetura em camadas é mais utilizado em sistemas grandes e complexos que necessitam de decomposição, pois frequentemente são compostos por operações de baixo e Pág. 28 de 91 alto nível. Deste modo, o agrupamento de tarefas comuns aumenta a escalabilidade e facilita a manutenção de camadas distintas. Estrutura Cada camada pode ser definida pela sua função e pelos vínculos que ela mantém com as outras camadas da aplicação. A estrutura desta arquitetura pode ser dividida em N-camadas, de acordo com a necessidade da aplicação. No entanto, o padrão mais utilizado é o de três camadas: interface com o usuário, lógica de negócio e acesso a dados. A figura a seguir ilustra este modelo. Figura 20 – Modelo de arquitetura em três camadas Fonte: Elaborado pelo autor. Aplicabilidade O padrão de arquitetura em camadas deve ser utilizado quando: • tarefas similares podem ser agrupadas para facilitar a manutenção; • a manutenção de um componente não afeta os outros; • as partes do sistema podem ser substituídas de modo independente; • há a possibilidade de utilizar recursos do sistema atual em projetos futuros; • componentes complexos precisam ser decompostos; • o limite entre os componentes é bem definido. • Consequências Vantagens: • Boa reusabilidade devido ao encapsulamento. • Suporte à padronização com a criação de frameworks. • As dependências são restritas à camada. Pág. 29 de 91 • Recursos intercambiáveis entre aplicações distintas. Desvantagens: • Queda de desempenho proporcional ao número de camadas. • Alterações de comportamento podem provocar efeito cascata e causar gargalos. Exemplo de aplicação Considere o seguinte cenário: uma empresa possui diversas bases de dados alimentadas de modo independente e deseja fornecer informações processadas por uma única regra de negócio para diferentes plataformas de acesso. Neste caso, a arquitetura em três camadas pode ser implementada permitindo o desenvolvimento de componentes específicos para bases distintas, interfaces adequadas para cada dispositivo e uma regra de negócios centralizada e compartilhada, conforme ilustrado na figura a seguir. Figura 21 – Arquitetura em camadas para o exemplo de múltiplas bases e múltiplas plataformas Camada de apresentação Camada de Lógica de Negócio Camada de Acesso a Dados Fonte: Elaborado pelo autor. Pág. 30 de 91 Padrão de arquitetura Pipes and filters ou Pipeline De acordo com Buschmann et al. (2001), o padrão arquitetural Pipes and filters, também denominado Pipeline, permite um processamento sequencial por meio de etapas encapsuladas em componentes chamados filters que, por sua vez, são acoplados por meio de conexões conhecidas como pipes. A figura 22 ilustra este padrão. Figura 22 – Arquitetura pipes and fi lters Pipes (Conexões) Data SinkData Source Filters (Componentes) Fonte: Elaborado pelo autor. Objetivo O padrão tem por objetivo decompor o sistema em etapas independentes, com o propósito de reutilizar esses módulos separadamente e facilitar a manutenção. Contexto Sistemas que processam sequências de dados e executam a mesma operação várias vezes podem fazer uso deste padrão para encapsular tarefas e reutilizá-las como etapas independentes, seja na mesma pipeline ou em outras. Estrutura De acordo com Buschmann et al. (2001), e conforme ilustrado na figura 22, a estrutura deste padrão é composta da seguinte forma: Pág. 31 de 91 • Filters: são componentes responsáveis por transformar dados de entrada. O processamento de um filter deve depender apenas desses dados,a fim de permitir sua utilização no desenvolvimento de outros sistemas. • Pipes: são as conexões entre filters, e são responsáveis pela transferência de dados e pela sincronização entre os componentes acoplados. • Data Source: fonte de dados sequenciais padronizados. • Data Sink: processo responsável por coletar o resultado final da sequência de processamento realizada pelos filters. Aplicabilidade O padrão pipes and filters pode ser aplicado nas seguintes condições: • Dados de entrada e saída padronizados. • Necessidade de processar dados sequenciais. • Etapas paralelas não devem compartilhar informação. • Deve ser viável realizar atualizações por meio de substituição parcial ou recombinação de filters. Consequências Vantagens: • Reutilização de filters em aplicações distintas. • Flexibilidade por meio da troca e recombinação de filters. • Possibilidade de processamento paralelo. • Desvantagens: • Compartilhamento de estado intricado. • Complexidade no tratamento de erros em cascata. • Perda de desempenho no processamento dos dados em sequências com muitas etapas. • Exemplo de aplicação Considere o seguinte cenário: um banco realiza diversas transações financeiras ao processar arquivos de texto padronizados. Cada transação possui uma configuração específica para os dados de entrada. No entanto, várias operações são comuns em transações distintas. Pág. 32 de 91 Figura 23 – Transações bancárias Fonte: vasabii/Shutterstock Neste exemplo, toda a entrada de dados será realizada via arquivos de texto, portanto, podemos utilizar um único componente para a leitura de arquivos. Contudo, as regras de negócio para cada transação são independentes, e devem ser empregados componentes específicos conforme a necessidade. Em uma etapa seguinte, todas as transações geram relatórios via outro componente compartilhado. Por fim, o componente Ler Arquivos é utilizado novamente para processar o conteúdo dos relatórios e disponibilizá-lo ao Data Sink. A figura 24 ilustra este exemplo. Figura 24 – Arquitetura Pipes and Filters para transação fi nanceira com múltiplas entradas e processos compartilhados Gerar Relatório Processar Transação A Processar Transação B Reuso de componentes Ler Arquivos Transação B Transação A Data Source Ler Arquivos Data Sink 1 1 1 1 1 1 1 1 11 1 1 1 0 0 0 0 0 0 0 0 0 00 0 0 1 1 1 1 1 1 1 1 11 1 1 1 0 0 0 0 0 0 0 0 0 00 0 0 Fonte: Elaborado pelo autor. Pág. 33 de 91 2.4. Padrão de arquitetura Blackboard O padrão Blackboard é utilizado para tratar problemas não determinísticos, como por exemplo, sistemas de inteligência artificial e reconhecimento de padrões. Neste padrão arquitetural, de acordo com Buschmann et al. (2001), diversos subsistemas unem seus conhecimentos para gerar uma possível solução parcial ou aproximada. Objetivo O objetivo do padrão arquitetural Blackboard é dividir um problema não determinístico entre subsistemas especializados para solucionar o objetivo de modo cooperativo. Contexto Este padrão pode ser aplicado em domínios imaturos ou complexos, compostos por diversos fatores sem solução algorítmica conhecida ou viável. Estrutura Este padrão é composto pelos seguintes itens: • Blackboard: central de armazenamento de dados; as possíveis soluções e os dados de controle são armazenados aqui. • Fontes de conhecimento: subsistemas independentes que resolvem aspectos específicos do problema. Nenhum deles pode resolver a tarefa do sistema sozinho; uma solução global só pode ser construída por meio da integração dos resultados de várias fontes de conhecimento. • Central de controle: monitora as alterações no Blackboard, coordena as funções das fontes de conhecimento e decide a tomada de ações de acordo com a estratégia da aplicação. A estratégia é definida de acordo com as informações obtidas sobre o domínio. Aplicabilidade O padrão Blackboard pode ser aplicado nas seguintes condições: • O problema possui dados incertos e admite soluções aproximadas. • A pesquisa completa do espaço de soluções possíveis não é viável em um tempo razoável. • O problema global pode ser resolvido através de soluções parciais, seguindo a abordagem “dividir para conquistar”. • Os dados de entrada e saída, parcial ou global, têm representações distintas e os algoritmos devem ser implementados de acordo com paradigmas específicos. Pág. 34 de 91 Figura 25 – “Dividir para conquistar” Fonte: Yury Zap/Shutterstock Consequências Vantagens: • Em domínios imaturos, há a possibilidade de experimentar diferentes algoritmos para mesma subtarefa, sem afetar as demais. • Algoritmos disjuntos induzem a aplicação de paralelismo potencial, ou seja, as fontes de conhecimento podem ser exploradas em paralelo, porém, o acesso a central de dados deve ser sincronizado e compartilhado. • Torna possível a experimentação com algoritmos distintos e também permite que diferentes heurísticas de controle sejam empregadas. • Suporta mutabilidade e manutenção, porque as fontes de conhecimento, a central de controle e a estrutura de dados central são rigorosamente separadas. • As fontes de conhecimento independentes são especializadas em tarefas específicas e podem ser reutilizadas em outras aplicações. • Tolerância a falhas e conclusões duvidosas. Desvantagens: • Testes complexos, devido à dificuldade de reproduzir resultados não determinísticos. • Não há garantia de uma solução ótima. • Dificuldade para estabelecer uma boa estratégia de controle. • Baixo desempenho computacional. • Grande esforço no processo de desenvolvimento. • Não suporta paralelismo integral. Pág. 35 de 91 Exemplo de aplicação Considere o seguinte cenário: uma aplicação de reconhecimento de voz (speech recognition) deve interpretar comandos, reconhecer o usuário e executar ações. Neste contexto, são empregados agentes distintos com as seguintes funções: interpretação de palavras isoladas, interpretação de comandos compostos como frases e reconhecimento de sinal de voz. Figura 26 – Speech recognition Fonte: Viktorus/Shutterstock Este problema pode ser modelado da seguinte forma: um codificador repassa o sinal para um agente de reconhecimento, que por sua vez verifica as permissões de acesso para o usuário. Caso o acesso seja liberado, o sinal é encaminhado para o interpretador de palavras, que transforma o sinal em texto e envia o conjunto de palavras para a verificação de comandos compostos. Nesta etapa, um agente verifica se o padrão formado pelo conjunto de palavras extraídas do sinal é compatível com algum comando armazenado na estrutura de dados central. Esta sequência de controle pode ser induzida por meio de heurística ou inferida com o auxílio de algoritmos de aprendizado. A figura 27 ilustra essa dinâmica. Pág. 36 de 91 Figura 27 – Arquitetura Blackboard para reconhecimento de comandos de voz Central de controle Central de Dados Fontes de Conhecimento Controle de Usuário Codi�cador de Sinal Interpretador de Palavras Comando: “Liberar acesso.” Sinal de Voz Interpretador de Comandos Fontes de Conhecimento Aa controle Fonte: Elaborado pelo autor. 2.5. Model-View-Controller (MVC) O padrão MVC é um modelo de camadas específico que divide a aplicação em três componentes: • o Model, ou Modelo, que contém as funções básicas e o acesso aos dados; • a View, ou visualizador, que exibe os dados para o usuário; e • o Controller, ou controlador, que gerencia a interação entre as entradas do usuário e os dados do sistema. Pág. 37 de 91 Figura 28 – MVC Fonte: Bakhtiar Zein/Shutterstock Objetivo O objetivo do padrão MVC é garantir a coerência entre a interface do usuário e o modelo de dados da aplicação. Contexto As aplicações interativas com uma interface homem-computador flexíveis podem fazer uso deste padrão arquitetural. Estrutura Este padrão é composto da seguinte forma, de acordo com Buschmann et al. (2001): • Model: encapsula o acessoaos dados e funções básicas da aplicação, fornecendo ao usuário procedimentos que executam tarefas específicas. • View: exibe para o usuário os dados fornecidos pelo controle e estabelece uma interface para interação entre o usuário e a aplicação. • Controller: interpreta eventos de entrada e envia requisições para o modelo de dados. Em seguida, processa os dados carregados a partir do modelo e envia para o visualizador. Pág. 38 de 91 Aplicabilidade O padrão MVC é muito flexível e pode ser aplicado em diversas situações. Como, por exemplo, nos casos em que: • o projeto da interface deve ser desenvolvido separadamente; • a mesma informação deve ser exibida em formas diferentes; • a interface e o comportamento da aplicação devem refletir os dados do modelo em tempo real; • alterações de interface devem ser simples e permitir mudanças em tempo real; • há a necessidade de implementar um mecanismo de propagação de mudança para manter a coerência entre o modelo e a interface; • é necessário suportar diferentes plataformas sem a necessidade de alterar a base da aplicação. Consequências Vantagens: • Múltiplas interfaces para o mesmo modelo. • Mecanismo de propagação de alteração entre o modelo e a interface. • Interfaces e controladores intercambiáveis em tempo real. • Criação de um possível framework. Desvantagens: • Aumento de complexidade. • Propagação de atualizações excessiva. • Alta dependência entre interface e controlador: ◊ Interfaces e controladores podem ser altamente dependentes do modelo. ◊ Interfaces e controladores podem ser altamente dependentes da plataforma. • Ineficiência de acesso a dados por meio de interfaces com múltiplas requisições. Exemplo de aplicação Figura 29 – Multiplataformas Fonte: ymgerman/Shutterstock Pág. 39 de 91 Considere um sistema de gerenciamento que atende todos os departamentos de uma determinada empresa, da produção até a presidência. Vários funcionários fornecem e consomem informações simultaneamente via plataformas e dispositivos distintos, como computadores, celulares e tablets. Porém, para cada setor, as informações devem ser visualizadas da forma mais conveniente. Ou seja, os operadores têm acesso a informações específicas do seu trabalho, os gestores controlam planilhas detalhadas da sua equipe e a diretoria visualiza apenas os balancetes. A figura 30 ilustra esta situação. Figura 30 – Arquitetura MVC aplicada à modelagem de um sistema de gerenciamento com base compartilhada e visualização personalizada Diretoria Gerência Produção View Controller Model Fonte: Elaborado pelo autor. SAIBA MAIS Almeida (2017) traz um exemplo de aplicação em Java sem e com o uso do modelo MVC. Vale a pena verificar, implementar e testar. Disponível no link: http://www.dsc.ufcg.edu.br/~jacques/cursos/map/html/arqu/mvc/mvc.htm >. Pág. 40 de 91 Padrão de arquitetura Microkernel O padrão arquitetural Microkernel se aplica a sistemas de software que devem ser capazes de se adaptar às necessidades de mudança do sistema. Ele separa um núcleo funcional mínimo de recursos estendidos e partes específicas. Este padrão também serve como um gerenciador para conectar essas extensões e coordenar a sua colaboração (BUSCHMANN et al., 2001). Objetivo O padrão Microkernel visa permitir o desenvolvimento de várias aplicações que utilizam interfaces de programação semelhantes e que compartilham as mesmas funcionalidades. Contexto Aplicações baseadas em módulos independentes podem utilizar o padrão Microkernel para gerenciar recursos adicionais. Estrutura A estrutura deste padrão é composta da seguinte forma: • Microkernel: é o principal componente deste padrão. Ele implementa serviços centrais como comunicação e gerenciamento de recursos. Os módulos acoplados ao sistema podem utilizar as funcionalidades fornecidas pelo Microkernel. • Servidores internos: componentes adicionais que estendem as funcionalidades do Microkernel. Servidores internos podem, portanto, encapsular algumas dependências no hardware subjacente ou sistema de software. • Servidores externos: componentes que utilizam os recursos do Microkernel para executar suas próprias interfaces. • Clientes: aplicativos associados a um servidor externo. Eles só acessam os recursos disponibilizados pelo servidor. • Adaptadores: proporcionam uma interface entre clientes e servidores externos, permitindo que os clientes acessem os serviços fornecidos independentemente da plataforma. Pág. 41 de 91 Figura 31 – Estrutura do padrão arquitetural Microkernel Internal Server A Client A Client B Client C Client D Internal Server B Microkernel External Server A External Server B Internal Server C Fonte: <http://suineg.info/microkernel-pattern/>. Aplicabilidade O padrão Microkernel pode ser aplicado nos seguintes casos: • A aplicação deve suportar módulos diferentes em uma única plataforma. • Os módulos podem ser categorizados em grupos que usam o mesmo núcleo funcional de diferentes maneiras. • A plataforma de aplicações deve lidar com o hardware e contínua evolução do software. • A plataforma de aplicação deve ser portável, extensível e adaptável para permitir uma fácil integração de tecnologias emergentes. • O núcleo funcional da aplicação deve ser separado em um componente com tamanho mínimo, e os módulos devem ser adicionados conforme a necessidade. Consequências Vantagens: • Portabilidade: na maioria dos casos, apenas o Microkernel precisa ser reescrito, e alterações de hardware demandam modificações apenas em suas dependências específicas. • Flexibilidade e extensão: uma das maiores vantagens desta arquitetura é a capacidade de extensão e adaptação do sistema por meio da inclusão de novos módulos sem a necessidade de alterações no núcleo. Pág. 42 de 91 • Separação de mecanismos e políticas: o Microkernel controla apenas os mecanismos básicos e permite que os módulos implementem suas políticas específicas. • Escalabilidade: altamente adaptável em sistemas distribuídos. • Confiabilidade: a tolerância a erros pode ser facilmente suportada porque os sistemas distribuídos permitem que se ocultem as falhas de um usuário. • Transparência: em sistemas distribuídos, a arquitetura Microkernel permite que cada componente acesse outros serviços sem a necessidade de saber a sua localização. Desvantagens: • Desempenho: softwares monolíticos com foco específico são mais eficientes que a arquitetura Microkernel. • Complexidade de concepção e implementação: a separação entre mecanismos e políticas requer um profundo conhecimento de domínio e um esforço considerável durante a análise de requisitos. Além disso, exige uma especificação meticulosa dos recursos disponibilizados pelo Microkernel. Exemplo de aplicação Suponha que você pretende desenvolver um novo sistema operacional para celulares que deverá atender aos seguintes requisitos: fácil portabilidade para qualquer aparelho, integração simplificada de novas aplicações e possibilidade de executar aplicativos de outros sistemas similares. Figura 32 – Arquitetura Microkernel aplicada ao projeto de um sistema operacional (SO) Aplicativos Microkernel - Drivers e Funções Básicas do SO Emuladores Aplicativos Emulados Fonte: Elaborado pelo autor. Pág. 43 de 91 Neste caso, você deverá implementar uma estrutura elementar mínima que permita a expansão dos recursos com a inclusão de módulos. A execução de aplicativos externos poderá ocorrer por meio de adaptadores ou emuladores, que tenham acesso aos recursos do núcleo principal do sistema operacional. A figura 32 ilustra a estrutura deste exemplo. SAIBA MAIS O padrão de arquitetura Microkernel (algumas vezes referenciado como padrão de arquitetura plug- in) é um padrão ideal para implementar aplicações baseadas em produtos. Este tipo de aplicação se refere a pacotes que são disponibilizados para download em versões como um produto de terceiros. Entretanto, muitas empresas também desenvolveme criam releases de aplicações na forma de produtos de software, notas de releases e recursos adicionais na forma de plug-ins. Leia mais em: <https://www.oreilly.com/ideas/software-architecture-patterns/page/4/microkernel-architecture>. Fonte: Richards (2015). 2.7. Padrão de arquitetura Reflection O padrão Reflection fornece um mecanismo para alterar a estrutura e o comportamento de sistemas de forma dinâmica. Neste padrão, a arquitetura é dividida em duas partes: um nível meta, que provê informações sobre as propriedades do sistema, e um nível base, que inclui a lógica da aplicação. Alterações realizadas em informações contidas no nível meta afetam o comportamento do nível base (BUSCHMANN et al., 2001). Objetivo O padrão Reflection busca a criação de sistemas que suportem a sua própria modificação sem a necessidade de alterar a estrutura lógica da aplicação. Contexto Sistemas que dependem de adaptações frequentes podem implementar este padrão arquitetural para facilitar o processo de modificação. Estrutura Este padrão é composto pelos seguintes itens: • Nível base: implementa a lógica da aplicação. Seus componentes representam os serviços fornecidos pelo sistema, o modelo de dados e a interface de usuário. E também específica a comunicação entre estas estruturas. Pág. 44 de 91 • Nível meta: é composto por um conjunto de objetos que encapsulam informações específicas sobre um único aspecto da estrutura, comportamento ou estado do nível de base. Aplicabilidade O padrão Reflection pode ser aplicado nos seguintes casos: • Adaptações constantes. • Possibilidade de alterações parametrizadas. • Necessidade de minimizar os efeitos colaterais de alterações invasivas. Consequências Vantagens: • Não há alterações explícitas no código-fonte. • Alterações no sistema são simplificadas por parâmetros. • Suporte para vários tipos de alterações parametrizadas. Desvantagens: • Modificações incorretas nos parâmetros do nível meta podem causar falhas. • Aumento do número de componentes proporcional à quantidade de parâmetros utilizados no nível meta. • Baixa eficiência causada pela interpretação dos parâmetros em tempo de execução. • Nem todas as alterações são suportadas por parâmetros. • Nem todas as linguagens de programação suportam esta arquitetura. Exemplo de aplicação Figura 33 – HTML Fonte: Rawpixel.com/Shutterstock Pág. 45 de 91 Considere uma aplicação que necessita ler páginas HTML de um site para armazenar informações contidas entre tags específicas. O layout desse site, assim como a estrutura das suas páginas, varia frequentemente, o que demanda alterações constantes na aplicação. Neste caso, é possível definir um nível meta capaz de parametrizar a extração de informações do site, permitindo que a aplicação seja adaptada sem alterações específicas no código-fonte. A figura 34 ilustra este exemplo para a arquitetura Reflection. Figura 34 – Arquitetura Reflection aplicada ao projeto de um sistema para extração de informações em websites com estrutura volátil Interface de Usuário Lógica de Negócio Website - Mutável Nível Base Modelo de Dados Parâmentros da Interface Parâmentros da Lógica Parâmentros do Modelo Nível Meta Fonte: Elaborado pelo autor. SAIBA MAIS Caelum (2017) traz um exemplo em Java de utilização do Reflection. Acesse o link: <http://www.caelum. com.br/apostila-java-testes-xml-design-patterns/reflection-e-annotations/>. Para ver um exemplo asp.net em Peixoto (2017) mostra um exemplo no link: <http://www.linhadecodigo. com.br/artigo/1518/entendendo-o-reflectionaspnet_csharp.aspx>. Pág. 46 de 91 3. DESIGN PATTERNS – PADRÕES DE CRIAÇÃO Neste capítulo, definiremos o que é um padrão de projeto, quais são os padrões adotados pela comunidade de desenvolvedores e como eles são classificados. Depois, definiremos o que são padrões de criação e apresentaremos os principais padrões de criação, bem como exemplos de uso. 3.1. Conceitos de padrões de projeto Os primeiros registros de design patterns ou padrões de projeto foram publicados por Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides, em 1994, no livro Design patterns: elements of reusable object-oriented software. Estes padrões ficaram conhecidos como padrões GoF – Gang of Four, em referência aos quatro autores. Figura 35 – Autores conhecidos como Gang of Four – GoF Fonte: <http://slideplayer.com/slide/7462984/24/images/4/Gang+of+Four+(GoF)+http:/ www.research.ibm.com/designpatterns/pubs/ddj-eip-award.htm..jpg>. Além dos padrões GoF, há diversos outros padrões de projeto. Craig Larman, em 2004, no seu livro Applying UML and patterns, reuniu um conjunto de padrões sob o acrônimo GRASP – General Responsibility Assignment Software Patterns (Padrões de Software para Atribuição de Responsabilidade Geral), que funcionam como um guia no desenvolvimento orientado a objetos, apoiando a organização por responsabilidades. O GRASP reúne nove padrões relevantes para o desenvolvimento de sistemas OO dirigidos por responsabilidades. Os nove padrões que compõem o GRASP são: Creator (criador); Information Expert (especialista); Low Coupling (baixo acoplamento); Controller (controlador); High Cohesion (alta coesão); Polymorphism (polimorfismo); Pure Fabrication (pura invenção); Indirection (indireção); Protected Variations (variações protegidas). Pág. 47 de 91 Os padrões de projeto descrevem os princípios fundamentais da atribuição de responsabilidades a objetos. Esses padrões exploram os princípios fundamentais de sistemas OO. Embora a adoção de padrões não seja trivial para um programador iniciante, este conceito tem sido adotado pela comunidade de desenvolvedores e cada vez mais exigido por líderes nas fábricas de software. Como visto anteriormente, um código-fonte de um programa pode degradar-se ao longo de sua vida. Por princípio, os padrões buscam isolar as partes do código que são estáveis daquelas que são/serão mais suscetíveis a mudanças. O resultado que se espera é ter aplicações nas quais seja mais fácil efetuar procedimentos de manutenção, cujo código seja mais facilmente compreendido pela equipe do projeto, aumentando assim seu tempo de vida e postergando o início da degradação. Os patterns são uma coleção de padrões de projeto de software, que oferecem soluções para problemas conhecidos e recorrentes no desenvolvimento de software. Um pattern descreve uma solução comprovada para um problema recorrente, dando ênfase ao contexto em que o problema ocorre e mostrando as consequências e o impacto de sua solução. Figura 36 – Design patterns Fonte: <https://www.linkedin.com/pulse/software-design-patterns-web-application-rajveer-gangwar>. Assim, os patterns são dispositivos que permitem que os programadores compartilhem conhecimento sobre o seu projeto. Pág. 48 de 91 Como desenvolvedores, sabemos que, quando programamos, encontramos muitos problemas que ocorrem, ocorreram e irão ocorrer novamente. A pergunta que sempre fazemos é: “Como vamos solucionar este problema desta vez?”. Documentar um padrão é uma maneira de podermos reusar e possivelmente compartilhar uma informação que aprendemos em relação à melhor maneira de se resolver um determinado problema de software. As vantagens de se utilizar design patterns são muitas e incluem as seguintes: • Os padrões já foram provados e refletem a experiência, o conhecimento e as soluções dos desenvolvedores que tiveram sucesso usando-os em seus trabalhos. • Os padrões são reutilizáveis e oferecem uma solução pronta que pode ser aplicada a diferentes problemas. • Os padrões proveem um vocabulário comum que pode expressar muitas soluções, de forma sucinta e objetiva. No entanto, é importante lembrar que os padrões, por si só, não garantem o sucesso do seu uso. A descrição do padrão indica quando ele pode ser aplicado, mas apenas a experiência pode determinar quando um padrão particular irá melhorar o projeto do sistema. O principalobjetivo de um design pattern é criar uma abstração de um problema recorrente e apresentar uma solução viável, além de poder compartilhar este conhecimento para que outras pessoas se beneficiem dele. Assim, a documentação de um design pattern deve ser feita de uma forma muito bem definida. De uma maneira geral, a documentação de um padrão inclui a definição dos seguintes itens: • Objetivos ou pré-requisitos que devem ser satisfeitos antes de se decidir aplicar um padrão. • A motivação ou o contexto em que o padrão se aplica. • Uma descrição da estrutura do programa em que o padrão será definido. • Consequências do uso do padrão, positivas e negativas. • Exemplos de uso e aplicação do padrão A figura 37 apresenta o mapa de padrões de projetos proposto pela Gang of Four. Parte destes padrões será abordada aqui. Pág. 49 de 91 Figura 37 – Mapa de padrões de projetos GoF Proxy adding operations changing skin versus guts sharing composites de�ning grammar adding operations composed using de�ning traversals avoiding hysteresis saving state of iteration creating composites adding responsibilities to objects de�nig algorithm’s steps con�gure factory dynamically single instance single instance implement using complex dependency management often uses de�ning the chain enumerating children sharing terminal symbols sharing states sharing strategies Composite Visitor Chain of Responsibility Mediator Interpreter State Strategy Flyweight Adapter Memento Bridge Template Method Observer Factory Method Façade Abstract Factory Prototype Singleton Decorator Command Builder Iterator Fonte: <https://i2.wp.com/www.dsc.ufcg.edu.br/~jacques/cursos/map/html/pat/relacoes.gif>. Pág. 50 de 91 O catálogo de padrões GoF contém 23 padrões e está, basicamente, dividido em três seções: • Creational (Padrões de criação). • Structural (Padrões estruturais). • Behavioral (Padrões comportamentais). Os padrões de projeto GoF são soluções para problemas recorrentes no desenvolvimento de sistemas de software orientado a objetos. O quadro 2 (veja a seguir) mostra a divisão destes padrões, no contexto da programação orientada a objetos. Os padrões de criação se referem à instanciação de objetos. Os estruturais estão ligados com a composição de classes ou objetos, e os comportamentais procuram caracterizar formas de interação entre classes e objetos. Um padrão GoF também é classificado segundo o seu escopo em 2 outros grupos: • Padrões com escopo de classe: definidos por relacionamentos de herança e em tempo de compilação. • Padrões com escopo de objeto: encontrados no relacionamento entre os objetos definidos em tempo de execução. Quadro 2 – Divisão dos padrões GoF Propósito 1. Criação 2. Estrutura 3. Comportamento Escopo Classe Factory Method Class Adapter Interpreter Template Method Objeto Abstract Factory Builder Prototype Singleton Object Adapter Bridge Composite Decorator Facade Flyweight Proxy Chain of Responsibility Command Iterator Mediator Memento Observer State Strategy Visitor Fonte: Brizeno (2016). A seguir, trataremos dos padrões de criação e, nos itens seguintes, apresentaremos os padrões estruturais e os comportamentais. Mas, antes de prosseguirmos, um pouco de humor... Pág. 51 de 91 Figura 38 – Tirinha Fonte: Schissato e Pereira (2012). Pág. 52 de 91 CURIOSIDADE Ainda para manter o bom humor, leia o trecho a seguir, sobre princípios comuns de design (SCHISSATO; PEREIRA, 2012, s/p): Há diversos princípios comuns de design, que, assim como os design patterns, tornaram-se boas práticas através dos anos e ajudaram que softwares de fácil manutenção pudessem ser construídos. A seguir, um resumo dos princípios mais conhecidos: Keep it simple, stupid (KISS) – Mantenha isso simples, estúpido Um problema comum em programação de software é a necessidade de complicar demais a solução. O objetivo do princípio KISS é manter o código simples, mas não simplista, evitando assim complexidade desnecessária. Don’t repeat yourself (DRY) – Não se repita O princípio do DRY é evitar a repetição de qualquer parte do sistema, abstraindo as coisas que são comuns entre si e colocando-as em um lugar único. Esse princípio não se preocupa somente com o código, mas com qualquer lógica que está duplicada no sistema. Tell, don’t ask – Fale, não pergunte O principio Tell, don’t ask está estreitamente alinhado com o encapsulamento e a atribuição de responsabilidades para as suas classes corretas. O princípio afirma que você deve dizer aos objetos quais ações você quer que eles realizem, em vez de fazer perguntas sobre o estado do objeto e então tomar uma decisão por si próprio em cima da ação que você quer realizar. Isso ajuda a alinhar as responsabilidades e evitar o forte acoplamento entre as classes. You ain’t gonna need it (YAGNI) – Você não vai precisar disso O princípio YAGNI se refere à necessidade de adicionar somente as funcionalidades que são necessárias para a aplicação e deixar de lado qualquer tentação de adicionar outras funcionalidades que você acha que precisa. 3.2. Definição de padrões de criação Padrões de criação auxiliam na concepção de sistemas independentes do modo como os objetos são gerados, compostos e representados. De acordo com Gamma et al. (1995), este tipo de padrão abstrai o processo de instanciação, alterando a classe instanciada por meio de herança. Pág. 53 de 91 Figura 39 – Detalhe da cena da criação, pintura da capela Sistina Fonte: Creative Lab/Shutterstock Assim, os padrões de criação concentram-se na composição de objetos complexos e no encapsulamento do comportamento de criação. Dessa forma, pretende-se evitar a ocorrência de códigos embutidos definindo pequenos grupos de características fundamentais, capazes de compor estruturas mais complexas. Os padrões de criação podem ser competitivos ou cooperativos. Algumas técnicas se complementam, enquanto outras executam funções similares de formas distintas. As cinco abordagens presentes no modelo GoF são apresentadas a seguir. 3.3. Padrão Abstract Factory Objetivo De acordo com Gamma et al. (1995), o objetivo do padrão Abstract Factory é fornecer uma interface para criar grupos de objetos relacionados aos dependentes sem especificar suas classes concretas. Figura 40 – Factory (fábrica) Fonte: brumhildich/Shutterstock Pág. 54 de 91 Contexto Produtos portáveis utilizam o conceito abstrato deste padrão para desvincular código fundamental da aplicação de recursos que são dependentes da plataforma. Estrutura Este padrão é composto pelos seguintes elementos: • Abstract Factory: declara uma interface para operações que criam objetos abstratos. • Concrete Factory: implementa operações específicas para criar objetos concretos. • Abstract Product: declara uma interface para cada tipo de objeto. • Concrete Product: implementa uma interface de Abstract Product para definir um objeto que pode ser criado por sua Concrete Factory correspondente. • Client: utiliza as interfaces declaradas pelo Abstract Factory e Abstract Product, sem se preocupar com as implementações concretas. Aplicabilidade O padrão Abstract Factory pode ser empregado nos seguintes casos: • Um sistema deve ser independente do modo como seus objetos são criados, compostos e representados. • Um sistema deve ser configurado com vários grupos distintos de objetos. • Alguns objetos relacionados foram projetados para serem utilizados em conjunto, e você precisa impor essa restrição. • Você quer fornecer uma biblioteca de classes e pretende revelar apenas suas interfaces, não suas implementações. Consequências Vantagens: • Isola as classes concretas e ajuda a controlar as classes de objetos que um aplicativo cria. • Torna fácil a troca de implementações específicas, pois a classe Concrete Factory aparece apenas onde é instanciada na aplicação. • Promove a consistência entre produtos, pois facilita a implementação deobjetos que foram projetados para trabalhar juntos. Pág. 55 de 91 Desvantagem: • Alta complexidade para suportar novos produtos. Este processo requer alteração do Abstract Factory e todas as subclasses. Exemplo de aplicação Considere um sistema de diagnóstico para telefones celulares. Os componentes principais executam funções similares, no entanto, cada fabricante emprega sua própria arquitetura de hardware e software. Neste caso, é possível desenvolver uma biblioteca abstrata genérica e implementar classes concretas adequadas para cada aparelho. Figura 41 – Diferentes arquiteturas de hardware e software de smartphones Fonte: Maxx-Sdudio/Shutterstock 3.4. Padrão Builder Objetivo De acordo com Gamma et al. (1995), o objetivo do padrão Builder é separar a construção de um objeto complexo da sua representação, de modo que o mesmo processo de construção possa criar diferentes representações. Figura 42 – Builders (construtores) Fonte: Syda Productions/Shutterstock Pág. 56 de 91 Contexto Sistemas capazes de gerar ações indeterminadas para uma única aplicação utilizam a estrutura modular deste padrão para permitir a implementação de soluções alternativas que utilizem recursos oriundos de uma fonte única. Estrutura Este padrão é composto pelos seguintes elementos: • Builder: especifica uma interface abstrata para a criação de módulosdo sistema; • Concrete Builder: cria e executa módulos por meio da interface do builder; controla a representação criada e fornece um meio para obtenção dos resultados. • Director: constrói o objeto principal utilizando a interface do builder. • Product: representa um módulo alternativo que inclui interfaces para geração do resultado final. Aplicabilidade Este padrão pode ser empregado nas seguintes situações: • O algoritmo para a criação de um objeto complexo deve ser independente dos módulos que compõem o sistema e como eles são montados. • O processo de construção deve permitir diferentes representações para o objeto construído. Consequências Vantagens: • Permite variar o resultado gerado por um sistema. Como o sistema é construído por meio de uma interface abstrata, tudo o que você precisa fazer para mudar a representação interna é definir um novo tipo de construtor. • Isola o código para a construção e representação. O padrão Builder melhora a modularidade, encapsulando a maneira como um objeto complexo é construído e representado. • Proporciona um melhor controle sobre o processo de criação de objetos, pois carrega o sistema passo a passo sob o controle do Director. Desvantagem: • Pelo fato de ser um padrão bastante flexível, pode resultar no uso redundante e mal planejado, impactando no entendimento do código gerado. Pág. 57 de 91 Exemplo de aplicação Considere um sistema para tratamento de imagens. Atualmente, existem diversos recursos e todos os anos surgem novos algoritmos que fornecem funções inovadoras. No entanto, o conceito fundamental dessas ferramentas é único: uma imagem original é fornecida e uma imagem tratada é obtida no final do processo. Portanto, a base do sistema precisa implementar apenas essas funções e a cada novo algoritmo que surge, um novo módulo deve ser desenvolvido com suas características específicas. Figura 43 – Exemplo de sistema para tratamento de imagens Fonte: <http://www.ibm.com/developerworks/br/library/os-datavis2/index.html>. 3.5. Padrão Factory Method Objetivo De acordo com Gamma et al. (1995), o objetivo do padrão Factory Method é definir uma interface para criação de objeto que permita que as subclasses decidam qual classe será instanciada. Contexto Pág. 58 de 91 Sistemas que manipulam um número variável de tipos de objetos podem utilizar este modelo devido à sua flexibilidade. O Factory Method permite que a aplicação final implemente o suporte aos objetos necessários. Estrutura O padrão Factory Method é composto pelos seguintes elementos: • Product: define a interface dos objetos criados por este padrão. • Concrete Product: implementa a interface do Product. • Creator: declara o método que retorna o objeto do tipo esperado. • Concrete Creator: sobrescreve o método original para retornar uma instância do objeto esperado, ou seja, o Concrete Product. Aplicabilidade Este padrão pode ser usado nas seguintes situações: • A aplicação não pode antecipar os tipos de objetos que devem ser criados. • Uma classe espera que suas subclasses especifiquem o tipo de objeto a ser criado. • As classes delegam a responsabilidade a uma das subclasses e você quer identificar de qual subclasse advém o conhecimento. Consequências Vantagens: • Provê meios para as subclasses estenderem as funções básicas. • Conecta hierarquias de classes paralelas que compartilham o conhecimento da operação. Desvantagens: • Pode ser que seja necessário que clientes tenham que fornecer subclasses da classe Creator somente para criar um objeto Concrete Product em particular. Exemplo de aplicação Considere uma aplicação que carrega vários tipos de arquivos distintos. As funções básicas de busca, leitura e gravação são as mesmas em qualquer situação. Contudo, cada arquivo possui características particulares que são definidas por subclasses específicas. Portanto, o aplicativo não sabe qual classe deve ser instanciada até que a subclasse, associada a um arquivo, informe-o. Pág. 59 de 91 Figura 44 – Arquivos acessados por diferentes dispositivos Fonte: Rawpixel.com/Shutterstock SAIBA MAIS Você pode ver um exemplo prático do padrão Factory Method no link: http://www.dpi.ufv.br/projetos/ apri/?page_id=673 Pág. 60 de 91 3.6. Padrão Prototype Objetivo De acordo com Gamma et al. (1995), o objetivo do padrão Prototype é especificar o tipo dos objetos por meio de uma instância-protótipo e criar novos objetos, através da cópia dessa instância. Figura 45 – Protótipo em 3D Fonte: Andrey Suslov/Shutterstock Contexto A estrutura deste padrão permite a implementação de variações de uma classe sem o uso de subclasses, por meio da criação de instâncias-protótipo com características específicas pré-definidas. Estrutura A estrutura deste padrão é composta pelos seguintes itens: • Prototype: declara uma interface para autoclonagem. • Concrete Prototype: implementa a operação de autoclonagem. • Client: cria objetos solicitando um clone ao protótipo. Aplicabilidade O padrão Prototype pode ser empregado nas seguintes situações: Pág. 61 de 91 • As classes a serem instanciadas são especificadas em tempo de execução, por exemplo, por carregamento dinâmico. • Para evitar a construção de hierarquias de classe paralelas. • Quando a instância de uma classe pode ter diferentes estados. Consequências Vantagens: • Especificar novos objetos por meio da estrutura, utilizando composições. • Reduzir o número de subclasses através da clonagem de um protótipo. • Adicionar e remover tipos de objetos em tempo de execução. Protótipos permitem incorporar uma classe nova em um sistema, simplesmente copiando um protótipo por meio do Client. • Especificar novos objetos através do valor das propriedades e não pela definição de subclasses; • Configurar aplicações com classes dinamicamente carregadas. Desvantagem: • A principal limitação deste padrão é que as subclasses de um protótipo devem implementar a operação de clonagem e isso nem sempre é possível, pois alguns objetos não suportam cópia ou possuem referenciais circulares. Exemplo de aplicação Considere o mesmo exemplo do Factory Method, em que um sistema deve trabalhar com vários tipos distintos de arquivos. A diferença é que, no modelo Prototype, não é necessário implementar uma nova classe para cada tipo de arquivo suportado pelo sistema. Essa distinção é feita por meio da configuração de parâmetros de um clone da instância-protótipo da classe principal. 3.7. Padrão Singleton Objetivo De acordo com Gamma et al. (1995), o objetivo do padrão Singleton é garantir que uma classetenha somente uma instância e fornecer um ponto global de acesso a ela. Pág. 62 de 91 Figura 46 – Ponto central de acesso Fonte: B. Melo/Shutterstock Contexto Este padrão é ideal para restringir o acesso a recursos limitados e compartilhados pelo sistema. Como por exemplo: conexão com o banco de dados, acesso à impressora, alteração de configurações centralizadas etc. Estrutura A estrutura deste padrão possui apenas um item denominado Singleton, que define uma instância de operações única, que oferece acesso aos outros recursos do sistema. Aplicabilidade O padrão Singleton é adequado nos seguintes casos: • Deve haver exatamente uma instância de uma classe acessível de qualquer ponto do sistema. • Quando a instância única for extensível por meio de subclasses e os clientes forem capazes de usar uma instância estendida sem modificar seu código. Pág. 63 de 91 Consequências Vantagens: • Controlar o acesso à instância única, definindo quando e como ela poderá ser acessada. • Reduz o uso de variáveis globais. • Permite refinamento de operações e de representações, através de classes estendidas. • Permite variar o número de instâncias mantendo o controle de acesso. • Mais flexível do que restrições geradas por operações de classe. Desvantagem: • Uma possível desvantagem do Singleton é que não é possível inibir o acesso à sua classe, deixando livre o acesso aos dados da classe. Exemplo de aplicação Considere que a sua aplicação tem acesso a um webservice para consulta de endereço via CEP. Entretanto, este serviço não permite consultas simultâneas de um mesmo cliente e o seu sistema possui diversas funções que realizam esta tarefa. Neste caso, o padrão Singleton pode ser implementado para restringir o acesso e gerenciar o retorno das consultas dentro do sistema. Figura 47 – Logotipo do sistema de busca de CEP Fonte: <https://www.maikonlyne.com.br/admin/useruploads/images/base-cep-correios-358801-mlb20417702919_092015-o.jpg>. SAIBA MAIS Você pode ver alguns exemplos de implementação dos padrões de criação na série Mão na massa, de Brizeno (2016), disponível no link a seguir: <https://brizeno.wordpress.com/category/padroes-de-projeto/>. Pág. 64 de 91 4. DESIGN PATTERNS – PADRÕES ESTRUTURAIS Neste capítulo, serão apresentados os padrões de projetos classificados como estruturais pela GoF. Observe que cada padrão é utilizado com um determinado propósito. A aplicabilidade e um exemplo de cada um deles também serão apresentados, visando um melhor entendimento. 4.1. Definição de padrões estruturais Os padrões estruturais se preocupam com a forma como classes e objetos são compostos para formar estruturas maiores. Este tipo de padrão de projeto descreve maneiras de compor objetos para obter novas funcionalidades. A flexibilidade obtida pela composição de objetos provém da capacidade de mudar a composição em tempo de execução. Se utilizar o modelo de análise no contexto da estrutura da solução, o arquiteto pode começar a definir os frameworks no contexto dos padrões estruturais. 4.2. Padrão Adapter Objetivo O padrão Adapter é usado quando queremos converter a interface de uma classe em outra interface utilizada por um cliente. Permite que certas classes trabalhem em conjunto, pois, de outra forma, seria impossível por causa de suas interfaces incompatíveis. Figura 48 – Adaptadores Fonte: Suphaksorn Thongwongboot/Shutterstock Pág. 65 de 91 Contexto Este padrão é bastante útil quando precisamos da comunicação entre classes que não podem trabalhar juntas devido à incompatibilidade de suas interfaces. Estrutura A estrutura deste padrão é composta pelos seguintes itens: • Cliente: colabora entre os objetos conforme a interface alvo. • Alvo: define a interface de domínio específico que o cliente utiliza. • Adapter: adapta a classe existente para ser utilizada pela classe Alvo. • Classe Existente: Define uma interface pré-existente que necessita ser adaptada. Aplicabilidade O padrão Adapter é utilizado quando: • Deseja-se usar uma classe existente e sua interface não está de acordo com o que a aplicação em desenvolvimento necessita. • É necessário criar uma classe reutilizável que trabalhe de forma cooperativa com classes que não sabemos qual comportamento deve exibir, ou seja, classes que podem não ter interfaces compatíveis. Consequências Vantagens: • Permite que uma aplicação utilize funcionalidades externas. • Uma classe Adapter implementa uma interface conhecida dos clientes e permite acesso a instâncias de uma classe não conhecida. • Um objeto Adapter provê a funcionalidade prometida por uma interface sem fixar a classe que de fato a implementa. Desvantagens: • Um Adapter de classe não funciona bem quando se deseja • adaptar uma hierarquia de classes. Pág. 66 de 91 Exemplo de aplicação Precisamos fazer uso de uma biblioteca de classes de um software comercial e não temos acesso ao código. Podemos utilizar um Adapter para servir de interface entre as classes da biblioteca e do aplicativo que está sendo desenvolvido. 4.3. Padrão Bridge Objetivo O padrão Bridge é usado para separar uma abstração da sua implementação, de modo que as duas possam ser modificadas de forma independente. Figura 49 – Separando modelo abstrato do modelo real Fonte: nostal6ie/Shutterstock Contexto Já estudamos os princípios conhecidos como SOLID. Estes princípios nos auxiliam a lidar com o acoplamento entre os objetos de um sistema, o que evita a degradação do código, em função das mudanças que ele sofre. O padrão Bridge é uma das formas de desacoplar objetos do sistema, porém este padrão propõe a separação entre os conceitos e as implementações, ou seja, podemos ter uma classe que representa o conceito de algo e outra que especifica o código desta classe. Estrutura A estrutura deste padrão é composta pelos seguintes itens: Pág. 67 de 91 • Abstração: define a interface de abstração e mantém uma referência a um objeto do tipo Implementador. • Abstracão Refinada: Estende a interface definida por Abstração. • Implementador: Estabelece a interface para classes de implementação. Não é preciso que a interface corresponda exatamente com a interface de Abstração. As duas podem ser diferentes. A interface de implementação geralmente fornece somente operações primitivas, enquanto cabe à Abstração a responsabilidade de definir as operações de alto nível a partir das primitivas. • Implementador Concreto: Implementação concreta da interface definida por um Implementador. Aplicabilidade Use o padrão de projeto Bridge sempre que: • quiser evitar uma ligação permanente entre a interface e a implementação; • uma alteração na implementação não puder afetar clientes; • implementações forem compartilhadas entre objetos desconhecidos do cliente. Consequências Vantagens: • Separa interface de implementação. • Melhora as hierarquias de abstração e implementação. • Esconde detalhes de implementação dos clientes. Exemplo de aplicação Figura 50 – Sistema gráfico de janelas portável para diversas plataformas Fonte: Georgejmclittle/Shutterstock Pág. 68 de 91 Imagine um sistema gráfico de janelas que deve ser portável para diversas plataformas. Neste sistema, são encontrados diversos tipos de janelas, ícones, caixas de diálogos etc. Estas janelas formam uma hierarquia que contém uma abstração das janelas (classe base). Normalmente, a portabilidade seria obtida criando-se especializações dos tipos de janelas para cada uma das plataformas suportadas. O problema com essa solução reside na complexidade da hierarquia gerada e na dependência de plataforma que existirá nos clientes do sistema. Através do padrão Bridge, a hierarquia que define os tipos de janelas é separada da hierarquia que contém a implementação. Desta forma, todas as operações de Janela são abstratas e suas implementações são escondidas dos clientes. Fonte: <https://pt.wikipedia.org/wiki/Bridge_(padr%C3%A3o_de_projeto_de_software)>.4.4. Padrão Decorator Objetivo O padrão Decorator permite adicionar e remover responsabilidades e/ou comportamentos de uma classe em tempo de execução, ou seja, dinamicamente. Para compreendê-lo melhor, é necessário sempre raciocinar em termos de objetos. Figura 51 – Objetos de Decoração Fonte: Africa Studio/Shutterstock Pág. 69 de 91 Contexto Utilizamos o Decorator quando queremos adicionar funcionalidades a uma classe em tempo de execução, ou seja, adicionar responsabilidades a um objeto, mas não à sua classe. Estrutura A estrutura deste padrão é composta pelos seguintes itens: • Componente: define a interface para objetos que podem ter comportamentos acrescentados a eles dinamicamente. • Componente Concreto: define um objeto para o qual comportamentos adicionais podem ser atribuídos. • Decorator: mantém uma referência para um objeto Componente. Define uma interface que segue a interface de Componente. • Decorator Concreto 1 e Decorator Concreto 2: acrescentam comportamentos ao componente. Aplicabilidade Quando desejamos inserir um comportamento adicional a uma classe, porém em tempo de execução. Por exemplo, em uma interface gráfica, quando se deseja acrescentar uma borda a um componente qualquer ou uma barra de rolagem a uma área de texto. Uma prática utilizando este padrão seria inserir o componente em outro objeto que adiciona a borda. Consequências Vantagens: • O uso do padrão Decorator evita a criação de uma grande quantidade de subclasses, permitindo a definição de classes simples, em que somente o que é principal será implementado, além da possibilidade de adicionar funcionalidades dinamicamente. Desvantagem: • Uma desvantagem do Decorator é que são geradas muitasclasses pequenas, o que pode criar uma grande dificuldade para um desenvolvedor que esteja tentando compreender o funcionamento da aplicação. SAIBA MAIS Você pode ver um exemplo de implementação do padrão Decorator em Java no link a seguir: <http:// www.devmedia.com.br/padrao-de-projeto-decorator-em-java/26238>. Pág. 70 de 91 Exemplo de aplicação Um caso de aplicação do Decorator é quando existem muitas variações de um objeto. Um exemplo: uma classe Janela com uma subclasse Janela com Borda. Caso haja a necessidade de janelas com rolagem, tem-se ainda Janela com Rolagem e Janela com Borda e Rolagem, entre outras combinações. Figura 52 – Opções de Janelas – discussão Fonte: winui/Shutterstock 4.5. Padrão Façade Objetivo De acordo com Gamma et al. (1995), o objetivo do padrão Façade é fornecer uma interface para um conjunto de interfaces em um subsistema. Façade define uma interface de nível mais alto, o que torna o subsistema mais fácil de ser usado. Pág. 71 de 91 Figura 53 – Interfaces para sistema – discussão Fonte: Billion Photos/Shutterstock Contexto Alguns sistemas possuem subsistemas complexos. O padrão Façade fornece uma interface simplificada para operar os subsistemas. Estrutura A estrutura deste padrão é composta pelos seguintes itens: • Façade: conhece quais classes do subsistema são responsáveis pelas requisições de um cliente e delega solicitações aos objetos apropriados dos subsistemas. • Classes de subsistema: (Classe X, ..., Classe Z) implementam as funcionalidades dos subsistemas. Pág. 72 de 91 Aplicabilidade Façades podem ser utilizados não somente para criar uma interface mais simples em termos de chamadas a métodos, mas também para reduzir o número de objetos com os quais o objeto cliente deve lidar (SHALLOWAY, 2004). Consequências Vantagens: • Isola os clientes dos componentes do subsistema, reduzindo o número de objetos com os quais o cliente tem que lidar. • Promove um acoplamento fraco entre o subsistema e seus clientes, o que permite variar os componentes do subsistema sem afetar os seus clientes. Exemplo de aplicação O padrão Façade pode ser utilizado para simplificar a integração do sistema Casa Segura com seus subsistemas. Geralmente um sistema de segurança residencial, como o Casa Segura, possui câmeras que podem ser acessadas via internet de qualquer ponto do mundo, além de controle de acesso por meio de fechaduras com biometria e diversos outros equipamentos que podem ser controlados remotamente pelos moradores. Um sistema que utiliza o padrão Façade facilita muito a interface com as classes que controlam estes dispositivos. Figura 54 – Sistema de home security Fonte: Andrey_Popov/Shutterstock Pág. 73 de 91 4.6. Padrão Proxy Objetivo O padrão Proxy tem como objetivo fornecer um objeto representante ou um marcador para que outro objeto controle o acesso a ele. Figura 55 – Marcador Fonte: Sailorr/Shutterstock Contexto Muitas vezes um aplicativo precisa continuar funcionando mesmo na ausência de um subsistema. Neste caso, o Proxy pode intermediar temporariamente esta relação. Estrutura A estrutura deste padrão é composta pelos seguintes itens: • Proxy: mantém uma referência que permite que o Proxy acesse o objeto real. Fornece uma interface idêntica ao objeto real para os clientes. • Cliente: quem consome o Proxy “pensando” que é um objeto real. • Objeto real: É o objeto real que um Proxy representa. Aplicabilidade O padrão Proxy é aplicado quando um sistema precisa consumir os recursos de um objeto, mas por algum motivo ele não está disponível. Uma solução, nesse caso, é utilizar um objeto que funcione como um substituto para o objeto indisponível, de forma que o sistema possa se comunicar com ele. O cliente passa a usar o objeto intermediário que possui uma referência a um objeto real que, quando estiver disponível, recebe as informações. Pág. 74 de 91 Consequências Vantagens: • Transparência, pois é utilizada a mesma sintaxe na comunicação entre o cliente e o objeto real. • Tratamento inteligente dos dados no cliente. • Maior eficiência com caching no cliente. Desvantagens: • Possível impacto na performance. • Fatores externos como queda da rede podem deixar o Proxy inoperante. Exemplo de aplicação Imagine o projeto da Casa Segura, no qual um conjunto de componentes devem se comunicar. O uso do Proxy neste contexto pode facilitar a interação entre os diferentes subsistemas, garantindo a disponibilidade de funções não criticas. Um Proxy pode conhecer as funcionalidades de objetos reais do sistema, porém na ausência de um deles, o dispositivo continuará funcionando. Figura 56 – Casa Segura Fonte: chombosan/Shutterstock SAIBA MAIS Você pode ver alguns exemplos de implementação dos padrões estruturais na série Mão na massa, de Brizeno (2016), disponível no link: <https://brizeno.wordpress.com/category/padroes-de-projeto/> Pág. 75 de 91 5. DESIGN PATTERNS – PADRÕES COMPORTAMENTAIS Neste capítulo, serão apresentados os padrões de projetos classificados como comportamentais pela GoF. Observe que os padrões classificados nesta categoria atuam na instanciação das classes que descrevem o comportamento de uma aplicação de software. 5.1. Definição de padrões comportamentais Os padrões classificados como comportamentais tratam da organização das linhas de código que focam nas relações entre os objetos, a fim de melhorar a comunicação entre eles. O objetivo é compartilhar um mesmo tipo de comportamento entre diferentes classes, fazendo uso de heranças de interfaces e classes abstratas. 5.2. Padrão Chain of Responsability Objetivo Um sistema desenvolvido sob o paradigma orientado a objetos trabalha com a interação entre os objetos que o compõem por meio de mensagens. O desenvolvedor do sistema precisa especificar qual objeto irá tratar uma requisição (mensagem). O padrão de projeto Chain of Responsability propõe uma particular maneira de tratar estas requisições. Figura 57 – Mensagens Fonte: Rawpixel.com/Shutterstock Pág. 76 de 91 Contexto O padrão Chain of Responsability auxilia na definição de responsabilidade e pode ser composto com outros padrões de projetos. Desta forma, vários objetos podem tratar umatarefa, permitindo uma divisão de responsabilidades de forma clara. Estrutura A estrutura deste padrão, de acordo com Gamma et al. (1995), é composta pelos seguintes itens: • Alimentador: define a interface para manipular as solicitações e implementa a referência ao sucessor. • Alimentador Concreto A, . . ., Alimentador Concreto N: manipula as solicitações pelas quais é responsável. Pode acessar seu sucessor. Se o Alimentador Concreto pode tratar a solicitação, ele assim o faz; caso contrário, ele repassa a solicitação para o seu sucessor. • Cliente: inicia a solicitação para um objeto Alimentador Concreto da cadeia. • Requisição: as instâncias de Requisição é que irão transportar as informações para os alimentadores executarem algo. Aplicabilidade Gamma et al. (1995) recomendam o uso deste padrão quando: • mais de um objeto pode tratar uma solicitação e este é desconhecido; • for necessário emitir um pedido de um dos vários objetos sem especificar o receptor de forma explícita; • o conjunto de objetos capaz de tratar da solicitação deve ser especificado dinamicamente. Consequências Vantagens: • Evita o acoplamento entre o transmissor de uma requisição e seus receptores, fazendo com que mais de um objeto tenha a chance de manipular uma requisição. • Encadeia os objetos receptores e passa a requisição ao longo desse fluxo até que um objeto possa manipulá-lo. • Favorece a flexibilidade. • Desvantagem: • Como a requisição não tem receptor explícito, a recepção não é garantida. Além disso, não há garantia de que a requisição será tratada, já que pode sair ao final da cadeia sem ter sido adequadamente tratada, a não ser que a cadeia esteja configurada corretamente. Pág. 77 de 91 Exemplo de aplicação Uma máquina que vende produtos necessita armazenar, em locais diferentes, cada tipo de moeda possível. Um objeto deve receber a moeda, mas se ele não for capaz de armazená-la no local correto, deve passá-la para outro objeto, de forma que a colocação correta da moeda seja obtida. Este é um exemplo de desacoplamento, pois, se alguém não resolver determinada tarefa, ocorre uma delegação da responsabilidade para outro objeto, de forma transparente. Figura 58 – Máquina de venda com coletores de moeda Fonte: Syda Productions/Shutterstock SAIBA MAIS Você pode ver exemplos de implementação do padrão Chain of Responsability nas linguagens C#, C++, Delphi, Java e PHP apresentados por Sourcemaking.com (2017a), no link: <http://sourcemaking.com/ design_patterns/chain_of_responsibility>. 5.3. Padrão Mediator Objetivo O padrão Mediator define um objeto que encapsula a interação entre um conjunto de objetos. Favorece um acoplamento fraco ao evitar que os objetos se refiram explicitamente uns aos outros, ou seja, gerencia as colaborações entre um grupo de objetos. Pág. 78 de 91 Figura 59 – Gerenciamento de colaboração entre grupos Fonte: IlkerErgun/Shutterstock Contexto Caso já tenha alguma experiência com programação em uma linguagem orientada a objetos, você sabe que um programa pode ter um número muito grande de classes e o código pode estar espalhado entre elas. Quanto maior o número de classes do projeto, mais complexa será a comunicação entre elas. Com o padrão Mediator, a comunicação entre os objetos é encapsulada, com um objeto Mediador reduzindo a dependência entre eles. Estrutura A estrutura deste padrão é composta pelos seguintes itens: • Mediator: define a interface para a comunicação entre objetos que se relacionam. • Concrete Mediator: implementa a interface e coordena a comunicação entre os objetos ligados. • Concrete Colleague: comunica-se com outros objetos através do Mediator. Aplicabilidade Aplicável em sistemas que possuem um conjunto de objetos que se comunicam de forma bem definida, mas complexa. Pág. 79 de 91 Consequências Vantagens: • Ao mudar um comportamento de diferentes classes, apenas o Mediator será afetado. • Um Mediator simplifica protocolos de objeto, uma vez que substitui uma interação muitos- para-muitos para interações um-para-muitos entre o Mediator e os seus colegas. • A política de comunicações está centralizada no Mediador e pode ser alterada sem mexer nos seus colegas. Desvantagem: • Um Mediator encapsula protocolos e pode tornar-se mais complexo do que qualquer colega individual. Isso pode tornar o Mediator muito complexo e com uma grande quantidade de código, o que dificultará a sua manutenção. Exemplo de aplicação A torre de controle de um aeroporto, de forma análoga, demonstra o uso deste padrão. Os pilotos dos aviões que se aproximam ou partem do aeroporto comunicam-se com a torre, em vez de se comunicarem entre si. As restrições que dizem quem pode pousar ou levantar voo são definidas pela torre. É importante notar que a torre não controla todo o voo de um avião. A sua função é apenas estabelecer as restrições relativas ao pouso e decolagem do aeroporto. Figura 60 – Torre de controle de um aeroporto Fonte: <http://cultura.secular.com.br/09-mai2007/aeroporto.html>. Pág. 80 de 91 5.4. Padrão Observer Objetivo O padrão Observer define uma dependência um-para-muitos entre objetos para que, quando um objeto mudar de estado, todos os seus dependentes sejam notificados e atualizados automaticamente (GAMMA et al., 1995). Figura 61 – Exemplo de dependência de um-para-muitos no dominó Fonte: IlkerErgun/Shutterstock Contexto Utilizado em situações em que há forte acoplamento de classes do tipo muitos-para-muitos. Um Observer conhece regiões críticas do sistema e pode reduzir os efeitos colaterais de uma manutenção no código. Estrutura A estrutura deste padrão é composta pelos seguintes itens: • Cliente: conhece seus observadores e fornece uma interface para comunicação. • Observador: define uma interface de atualização para objetos que devem ser notificados de uma mudança. • Classe Concreta: guarda os estados definidos e envia notificações para seus observadores. • Classe Observadora: mantém a referência para a Classe Concreta. Implementa o Observador. Pág. 81 de 91 Aplicabilidade O padrão Observer pode ser utilizado quando: • uma abstração tem dois aspectos dependentes um do outro. Encapsular estes aspectos em objetos separados permite reusá-los independentemente; • uma mudança em um objeto exige mudar os outros e você não sabe quantos objetos serão afetados. Consequências Vantagens: • Pode-se reutilizar tanto observadores quanto sujeitos observados, assim como alterar sua interface e implementação sem afetar o sistema. • O uso de interfaces e classes abstratas reduz o acoplamento forte implicado pelo relacionamento bidirecional. Desvantagens: • O abuso pode causar sério impacto na performance. • Inundação de requisições (“tempestade de eventos”), características de sistemas em que todos notificam todos a cada mudança. Exemplo de aplicação Podemos utilizar como exemplo o sistema do projeto Casa Segura. Muito provavelmente, se o sistema for desenvolvido apoiado no paradigma orientado a objetos, seu modelo estrutural será composto por diferentes classes. Tais classes se relacionam entre si e partes do sistema serão alteradas ao longo do seu ciclo de vida. Sabe-se que uma alteração em uma classe pode afetar outras classes que dependem dela e, no processo de desenvolvimento, criamos dependências do tipo muitos-para-muitos, ou seja, muitas classes dependendo de muitas outras classes. Utilizar o padrão Observer auxiliaria na manutenção desse processo, visto que implementaríamos um “Observador” para gerenciar esta complexidade. Este Observador ficaria responsável por conhecer tais mudanças e comunicá-las às demais classes. Pág. 82 de 91 Figura 62 – Câmera de segurança funcionando como um observador Fonte: Sensay/Shutterstock 5.5. Padrão Strategy Objetivo O padrão Strategy define uma família de algoritmos, encapsula cada um e os torna intercambiáveis. Este padrão permite que o algoritmo varie independentementedos clientes que o utilizam. Figura 63 – Exemplo de família de algoritmos Fonte: isak55/Shutterstock Contexto Este padrão é utilizado quando se deseja que um algoritmo trate de forma diferente os dados submetidos a ele, ou seja, de acordo com um contexto, o algoritmo apresentará um comportamento. Pág. 83 de 91 Estrutura A estrutura deste padrão é composta pelos seguintes itens: • Estratégia: define uma interface comum para todos os algoritmos suportados. • Estratégia Concreta A e Estratégia Concreta B: implementa o algoritmo usando a interface de Estratégia. Aplicabilidade Este padrão é utilizado quando: • classes relacionadas forem diferentes apenas no seu comportamento; • são necessárias diferentes variações de um mesmo algoritmo; • um algoritmo usa dados que o cliente não deve conhecer; • uma classe define muitos comportamentos e estes aparecem como múltiplas declarações condicionais em suas operações. Consequências Vantagens: • Conjunto de algoritmos relacionados. • É uma alternativa ao uso de subclasses. • Elimina comandos condicionais. Desvantagem: • Gera um aumento do número de objetos no sistema. Exemplo de aplicação Suponha um banco que deva praticar uma taxa para cada tipo de cliente conforme o risco que representa para a instituição. Teríamos que implementar, para cada tipo de cliente, um algoritmo diferente e, para cada novo tipo, realizar um conjunto de alterações. O padrão Strategy pode definir os tipos de clientes, mas sua implementação deve estar em outras classes que as implementam. Pág. 84 de 91 Figura 64 – Diferentes taxas de risco Fonte: Rei and Motion Studio/Shutterstock 5.6. Padrão Visitor Objetivo De acordo com Gamma et al. (1995), este padrão representa uma operação a ser realizada nos elementos de um objeto. O Visitor permite definir uma nova operação sem mudar as classes dos elementos nos quais opera. Dentre os princípios de modelagem SOLID, o padrão Visitor contribui no atendimento ao princípio OCP, em que uma classe deve estar “aberta” para extensão, porém fechada para modificação. Contexto O Visitor permite plugar novas funcionalidades em objetos sem precisar alterar a estrutura de herança. É utilizado para evitar espalhamento e fragmentação de código. Estrutura A estrutura deste padrão é composta pelos seguintes itens: • Visitor: declara uma operação de visita para cada classe de Elemento Concreto na estrutura do objeto. O nome da operação e a assinatura identificam a classe que envia o pedido de visita ao visitante. Isso permite que o visitante determine a classe concreta do elemento sendo visitado. Em seguida, o visitante pode acessar o elemento diretamente por meio de sua interface particular. Pág. 85 de 91 • Visitor Concreto: implementa as operações declaradas pelo Visitor. Fornece o contexto para o algoritmo e armazena seu estado local. Este estado frequentemente acumula resultados durante a mudança da estrutura. • Elemento Concreto: após a validação de mudança de estrutura, ele leva o Visitor como argumento. • Estrutura Objeto: fornece uma interface para permitir que o visitante realize consulta em seus elementos. Aplicabilidade Este padrão, de acordo com Gamma et al. (1995), é utilizado quando: • uma estrutura de objetos contém muitas classes de objetos com diferentes interfaces e você deseja realizar operações sobre esses objetos, que dependem de suas classes concretas; • a estrutura do objeto é compartilhada por muitas aplicações; • as classes que definem a estrutura de objetos raramente mudam, mas muitas vezes você deseja definir novas operações sobre a estrutura. Consequências Vantagens: • Facilita a adição de novas operações. • Agrupa operações relacionadas e separa operações não relacionadas, reduzindo o espalhamento de funcionalidades e o entrelaçamento do código. Desvantagens: • Exige grande trabalho para adicionar novos elementos na hierarquia, exigindo alterações em todos os Visitors; se a estrutura muda com frequência, é recomendado não utilizá-lo. • Quebra de encapsulamento, pois métodos e dados usados pelo Visitor devem estar acessíveis. Exemplo de aplicação No contexto do sistema para o projeto Casa Segura, o padrão Visitor deve ser utilizado para evitar o espalhamento e entrelaçamento de código. SAIBA MAIS Você pode ver exemplos de implementação do padrão Visitor nas linguagens C#, C++, Delphi, Java e PHP apresentados por Sourcemaking.com (2017b), no link: <https://sourcemaking.com/design_patterns/visitor>. Você pode ver alguns exemplos de implementação dos padrões comportamentais na série Mão na massa, de Brizeno (2016), disponível no link: <https://brizeno.wordpress.com/category/padroes-de-projeto/>. Pág. 86 de 91 CONSIDERAÇÕES FINAIS Este conteúdo apresentou algumas barreiras que a área de desenvolvimento de software ainda deve superar. Sabe-se que muitos projetos de desenvolvimento de software têm sido afetados pela complexidade crescente, e isso tem impactado o cronograma e o custo dos projetos de desenvolvimento, bem como dificultado a customização de sistemas. Os princípios de atribuição de responsabilidade em sistemas OO, os padrões de arquitetura de software e os design patterns apresentados aqui são ferramentas essenciais para o arquiteto de software. Se você é um desenvolvedor ou líder de equipe e ainda não está familiarizado com os temas tratados até aqui, sugerimos que se dedique bastante ao assunto, utilizando este e outros materiais sobre os temas. Este conhecimento certamente lhe proporcionará um diferencial muito importante na sua carreira. Como consequência direta do seu empenho, você verá, em pouco tempo, que os softwares desenvolvidos por você ou por sua equipe ganharão notoriedade. Iniciamos aqui uma discussão sobre modelagem de software, porém as ferramentas adequadas para a criação de modelos estáticos e dinâmicos de um sistema de software serão apresentadas em outros conteúdos. Um dos tópicos fundamentais é o uso da UML. Associar a UML com tudo o que falamos aqui é uma importante conexão que você deve fazer. Fique atento! Caso tenha sentido dificuldades em entender os conceitos abordados aqui, saiba que eles não são um tópico trivial de estudo. A compreensão dos padrões de projeto exige um alto nível de abstração dos conceitos de orientação a objetos, bem como certa vivência em desenvolvimento de software. Esperamos que os tópicos abordados tenham sido significativos para você e desejamos sucesso nos próximos módulos do nosso conteúdo. Continue contando conosco. Prof . Luciano Gaspar e Profa . Elisamara de Oliveira Pág. 87 de 91 GLOSSÁRIO DE SIGLAS CASE – Computer-aided Software Engineering ES – Engenharia de Software GoF – Gang of Four (referência aos autores Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides) LSP – Princípio de Substituição de Liskov MVC – Model-View-Controller OCP – Open/Closed Principle (Princípio Aberto-Fechado) OMG – Object Management Group OMT – Object Modeling Technique OO – Orientação a objetos OOSE – Object-oriented Software Engineering POO – Programação Orientada a Objetos SRP – Single Responsability Principle (Princípio de Responsabilidade Única) SO – Sistema Operacional UML – Unified Modeling Language (linguagem de modelagem unificada) Pág. 88 de 91 GLOSSÁRIO Algoritmo determinístico: o resultado de cada operação é definido de forma única (ZIVIANI, 2011, p. 409). Algoritmo não determinístico: capaz de escolher uma dentre as várias alternativas possíveis a cada passo. Algoritmos não determinísticos contêm operações cujo resultado não é unicamente definido, ainda que limitado a um conjunto especificado de possibilidades (ZIVIANI, 2011, p. 409). Pág. 89 de 91 BIBLIOGRAFIA ALMEIDA, Rodrigo R. Model-View-Controller (MVC) . 2017. Disponível em: <http://www.dsc.ufcg.edu. br/~jacques/cursos/map/html/arqu/mvc/mvc.htm>. Acesso em: 25 jun. 2017. BASS, L.; CLEMENTS, P; KAZMAN, R. Software architecture in practice.Boston: Addison-Wesley, 2003. BLAHA, M. R.; RUMBAUGH, J. R. Modelagem e projetos baseados em objetos com UML 2. Rio de Janeiro: Elsevier, 2006. BOOCH, G.; RUMBAUGH, J.; JACOBSON, I. UML – guia do usuário. Rio de Janeiro: Campus, 2006. BRIZENO, Marcos. Padrões de projeto. 2016. Disponível em: <https://brizeno.wordpress.com/category/ padroes-de-projeto/>. Acesso em: 25 jun. 2017. BROOKS, F. P. No silver bullet: essence and accidents of software engineering. IEEE Computer Magazine, Chapel Hill, 1987. BROOKS, F. P. The mythical man-month: essays on software engineering. 20. anniversary ed. Boston: Addison-Wesley, 1995. BUSCHMANN, F. et al. A system of patterns: pattern-oriented software architecture . Chichester: John Wiley & Sons, 2001. Disponível em: <http://ff.tu-sofia.bg/~bogi/France/SoftEng/books/Wiley%20-%20 Pattern-Oriented%20Software%20Architecture%20-%20Volume%201,%20A%20System%20of%20Patterns.pdf>. Acesso em: 19 jun. 2017. CAELUM. Reflection e annotations. 2017. Disponível em: <http://www.caelum.com.br/apostila-java-testes- xml-design-patterns/reflection-e-annotations/>. Acesso em: 25 jun. 2017. FREEMAN, Eric; FREEMAN, Elisabeth. Use a cabeça! Padrões de projeto . Rio de Janeiro: Alta Books, 2009 . GAMMA, E.; HELM, R.; JOHNSON, R.; VLISSIDES, J. Design patterns: elements of reusable object- oriented software. Reading: Addison-Wesley, 1995. Pág. 90 de 91 GAMMA, E.; HELM, R.; JOHNSON, R.; VLISSIDES, J. Padrões de projeto: soluções reutilizáveis de software orientado a objetos. Porto Alegre: Bookman, 2007. GARLAN, D. Software architecture: a roadmap. In: International Conference on Software engineering: Future of SE Track, p. 91-101, Limerick, Irlanda, 2000. GUEDES, Gilleanes T. A. UML 2 – Guia prático. São Paulo: Novatec, 2014. Disponível em: <https:// books.google.com.br/books/about/UML_2_GUIA_PRATICO.html?id=UacZiyoSiw8C&redir_esc=y>. Acesso em: 19 jun. 2017. HASTIE, Shane; WOJEWODA, Stephane. Standish group 2015 Chaos Report – Q&A with Jennifer Lynch . 2015. Disponível em: <https://www.infoq.com/articles/standish-chaos-2015>. Acesso em: 19 jun. 2017. LARMAN, Craig. Applying UML and patterns: an introduction to object-oriented analysis and design and iterative development. 1. ed. Upper Saddle River: Prentice Hall, 2004. LARMAN, Craig. Utilizando UML e padrões. 3. ed. Porto Alegre: Bookman, 2007. LAUDON, K. C.; LAUDON, J. P. Management information systems: managing the digital firm. 11. ed. Upper Saddle River: Prentice Hall, 2010. MARABESI. SOLID. 2016. Disponível em: <https://marabesi.com/oop/2016/04/12/s-o-l-i-d.html>. Acesso em 25 jun. 2017. MARTIN, Robert C. Agile software development, principles, patterns, and practices. Upper Saddle River: Prentice Hall, 2003. OLORUNTOBA, Samuel. SOLID: the first 5 principles of object oriented design. 2015. Disponível em: <https://scotch.io/bar-talk/s-o-l-i-d-the-first-five-principles-of-object-oriented-design>. Acesso em 26 jun. 2017. PEIXOTO, Francke. Entendendo o Reflection – ASP .NET/C# . 2017. Disponível em: <http://www. linhadecodigo.com.br/artigo/1518/entendendo-o-reflection-aspnet_csharp.aspx>. Acesso em: 25 jun. 2017. PRESSMAN, Roger S. Engenharia de software – uma abordagem profissional. 7. ed. São Paulo, Porto Alegre: AMGH, 2011. Pág. 91 de 91 RICHARDS, Mark. Microkernel architecture. 2015. Disponível em: https://www.oreilly.com/ideas/software- architecture-patterns/page/4/microkernel-architecture.> Acesso em: 26 jun. 2017. RUMBAUGH, James et al. Modelagem e projeto baseados em objetos. Rio de Janeiro: Campus, 2006. SCHISSATO, Jéssica; PEREIRA, Rodolfo. O que são design patterns? 2012. Disponível em: <http:// www.princiweb.com.br/blog/programacao/design-patterns/o-que-sao-design-patterns.html.> Acesso em: 20 jun. 2017. SHALLOWAY, A.; TROTT, J. R. Explicando padrões de projeto: uma nova perspectiva em projeto orientado a objeto. Porto Alegre: Bookman, 2004. SHAW, M.; CLEMENTS, P. A Field Guide to Boxology: Preliminary Classification of Architectural Styles for Software Systems. Proceedings of Computer Software and Applications Conference (COMPSAC ‘97), The Twenty-First Annual International. Washington, p. 6-13, 1997. SOURCEMAKING.COM. Chain of Responsibility. 2017a. Disponível em: <https://sourcemaking.com/ design_patterns/chain_of_responsibility>. Acesso em: Acesso em 26 jun. 2017. SOURCEMAKING.COM. Visitor design pattern. 2017b. Disponível em: <https://sourcemaking.com/ design_patterns/visitor>. Acesso em: 26 jun. 2017. ZACHMAN, J. Enterprise architecture: the issue of the century. Database Programming and Design Magazine, San Francisco, CA, 1997. ZIVIANI, Nívio. Projeto de algoritmos – com implementação em Pascal e C. 3. ed. São Paulo: Cengage Learning, 2011.