Baixe o app para aproveitar ainda mais
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 de
Compartilhar