Baixe o app para aproveitar ainda mais
Prévia do material em texto
ARQUITETURA DE SISTEMAS Aline Maciel Zenker Princípios de projeto e modelagem SOLID Objetivos de aprendizagem Ao final deste texto, você deve apresentar os seguintes aprendizados: � Explicar a importância dos princípios SOLID no projeto e na mode- lagem de sistemas. � Descrever o propósito de cada princípio SOLID, com exemplos. � Relacionar os princípios SOLID com padrões de arquitetura e padrões de projeto. Introdução No desenvolvimento de projetos, a preocupação com a qualidade é essencial. Criar um código padronizado, seguindo as boas práticas de programação, reduz sua complexidade e o acoplamento entre classes, facilitando a manutenção futura do sistema desenvolvido. Existem padrões que dão referência, servem de modelo para a aplicação, normalizam o código e criam boas práticas, além de comunicarem e organizarem os componentes do sistema. Esses padrões de arquitetura e de projeto (design patterns) possuem o papel de facilitar a implementação e resolver problemas por meio de soluções conhecidas, desenvolvidas e já testadas. Porém, apesar de trazerem melhorias aos sistemas, para aplicar tais pa- drões, é necessário muito conhecimento. Analistas e programadores apresentam dificuldades durante a aprendizagem desses métodos, prin- cipalmente em uma primeira abordagem sobre os padrões de projeto existentes. Assim, além dos padrões, o desenvolvedor pode contar com um conjunto de princípios de boas práticas da programação orientada a objetos chamado SOLID. O uso desses princípios melhora o código e facilita a aplicação dos padrões de projetos. Neste capítulo, você vai verificar a importância dos princípios SOLID no projeto, compreendendo qual é o propósito de cada princípio e como eles se relacionam com os padrões de arquitetura e os padrões de projeto. A importância dos princípios SOLID na gestão da qualidade em software Garantir um software com qualidade, por meio da gestão da qualidade em software, depende de diversos fatores e processos a serem aplicados no desen- volvimento do mesmo. Há quem diga que a qualidade de software está ligada a uma programação bem codificada, com notações e regras bem definidas — porém, a qualidade está muito além disso. De acordo com Pressman (2016, p. 414), a qualidade de software pode ser definida como “[...] uma gestão de qualidade efetiva aplicada de modo a criar um produto útil que forneça valor mensurável para aqueles que o produzem e para aqueles que o utilizam”. A qualidade é a missão de qualquer desenvolvedor que realmente se im- porta com o produto final do software. Dentre as várias formas de garantir essa qualidade, existem princípios específicos para gerar um código “sólido”, denominados princípios SOLID. Tais princípios propõem boas práticas de programação orientada a objetos, com o objetivo de diminuir o acoplamento entre classes, separar a responsabilidade de cada classe e torná-las coesas, visando a uma gestão de dependências. O termo SOLID é um acrônimo criado por Michael Feathers, identificado por Robert Cecil Martin, ou Uncle Bob, em 2000, e representa os cinco prin- cípios da programação orientada a objetos. Os princípios SOLID servem de base para diversos padrões de arquitetura e padrões de projeto, tornando mais fácil a manutenção do código. Acoplamento: o grau em que uma classe conhece as outras e depende de outras. Se o conhecimento da classe A sobre a classe B se der por meio de sua interface, temos um baixo acoplamento, o que é favorável ao código. Porém, se a classe A depende de membros da classe B que não fazem parte da interface de B, o acoplamento é alto, o que não favorece a aplicação. Coesão: quando há uma classe elaborada de forma que tenha um único e bem focado propósito, dizemos que ela tem uma alta coesão, o que é favorável à aplicação. Porém, quando há uma classe com propósitos que não pertencem apenas a ela, temos uma baixa coesão, o que não favorece a aplicação. Princípios de projeto e modelagem SOLID2 Utilizar os princípios SOLID traz alguns benefícios para a aplicação, como: � facilidade na manutenção do código, na adaptação e nos ajustes das alterações de escopo; � arquitetura aberta, apta a receber atualizações, melhorias e novos re- cursos sem danos colaterais; � facilidade na aplicação de testes e no seu entendimento; � maior reaproveitamento de código; � melhor adaptação a mudanças no escopo do projeto. Alguns dos problemas que podem ser evitados ao aplicar SOLID no projeto são: � código fora de padrão, sem estrutura; � dificuldades de isolar funcionalidades; � dificuldade na criação de testes de unidade; � muitos códigos duplicados, trazendo transtornos na adaptação do código; � quando houver mudança no código, o mesmo pode ser danificado, quebrado, por ter uma baixa coesão. Michael Feathers criou o acrônimo SOLID para sinalizar que o uso dos princípios torna o código sólido e organizado. Veja, a seguir, os princípios que formam o acrônimo SOLID. � Single responsibility principle (SRP) — princípio da responsabilidade única. � Open/closed principle (OCP) — princípio do aberto/fechado. � Liskov substitution principle (LSP) — princípio da substituição de Liskov. � Interface segregation principle (ISP) — princípio da segregação de interfaces. � Dependency inversion principle (DIP) — princípio da inversão de dependências. Propósitos SOLID A seguir, serão descritos os cinco princípios aplicados à programação orientada a objetos que formam o acrônimo SOLID. 3Princípios de projeto e modelagem SOLID Princípio da responsabilidade única (SRP) De acordo com o SRP, uma classe deve ter um, e somente um, motivo para mudar. Isso significa que a classe deve ser coesa. Cada responsabilidade, ação ou método deve ser de uma classe. Segundo Martin (2009), é importante separar responsabilidades em diferentes classes, pois a responsabilidade é um eixo para mudanças. Quando os requisitos mudam, essa mudança se manifestará por meio de alterações na responsabilidade, juntamente com a classe. Se uma classe tem mais de uma razão para mudar, então, a responsabilidade se torna acoplada. Alterações em uma responsabilidade podem prejudicar ou inibir a habilidade da classe em lidar com outras responsabilidades. Esse tipo de acoplamento leva os projetos a apresentarem falhas de maneiras inesperadas quando alterados. Veja os benefícios do SRP: � complexidade do código reduzida, mais explícita e direta; � facilitação da legibilidade; � redução de acoplamento; � código limpo e testável; � facilidade de evolução. Para Aniche (1998, p. 20): É realmente difícil enxergar a responsabilidade de uma classe. Talvez essa seja a maior dúvida na hora de se pensar em códigos coesos. É fácil entender que a classe deve ter apenas uma responsabilidade. O difícil é definir o que é uma responsabilidade, afinal é algo totalmente subjetivo. Por isso colocamos mais código do que deveríamos nela. Dois comportamentos “pertencem” ao mesmo conceito / responsabilidade se ambos mudam juntos. Lembrando que cada responsabilidade deve ser uma classe, porque uma responsabilidade é um eixo de mudança. Vamos analisar o exemplo da Figura 1, que apresenta a classe DebitoContaCorrente desenvolvida com a lingua- gem Java, paradigma OO (orientada a objetos). Ela possui uma baixa coesão, por possuir muitas responsabilidades. Princípios de projeto e modelagem SOLID4 Figura 1. Classe DebitoContaCorrente. A classe DebitoContaCorrente tem três responsabilidades: � validarSaldo(); � debitarConta(); � emitirComprovante(). Isso significa que uma classe é responsável pelas três ações: valida o saldo em conta, debita um valor da conta e, por fim, emite o comprovante. Supondo que, por algum motivo, a regra de negócio da emissão de comprovante mude, logo, será necessária uma mudança (adaptação) no código da classe Debito- ContaCorrente. Se no momento do refatoramento do código houver um bug, não será apenas a função de emissão de comprovantes que será afetada:as outras duas funções também serão afetadas, deixando o sistema sem três responsabilidades importantes. O sistema deixaria de fazer o débito e a va- lidação de saldo, devido a um problema com o comprovante. Esse problema poderia ser facilmente resolvido se o princípio SRP fosse aplicado desde o início do projeto. Veja o exemplo da Figura 2, que apresenta três classes: DebitoConta- Corrente, SaldoContaCorrente e ComprovanteContaCorrente. A aplicação foi desenvolvida com a linguagem Java, paradigma OO (orientada a objetos). O exemplo não apresenta apenas uma classe com três funções (responsabilidades), mas três classes, cada qual com sua funcionalidade única, trazendo ao projeto uma alta coesão, com um código separado por responsa- bilidades, com facilidade de manutenção. 5Princípios de projeto e modelagem SOLID Figura 2. Classes DebitoContaCorrente, SaldoContaCorrente e ComprovanteContaCorrente. As Figuras 1 e 2 são exemplos simples de como o acoplamento pode trazer problemas. Quanto maior o número de responsabilidades em uma classe, mais complexo será seu teste unitário, além de aumentar consideravelmente o número de testes de interação a serem executados. O SRD é um dos mais importantes princípios do SOLID e deve ser aplicado para se obter classes mais coesas e de baixo acoplamento. Princípio do aberto/fechado (OCP) O OCP possibilita estender um comportamento de uma classe sem a necessi- dade de modificá-lo. Aniche (1998) o define como um conceito que possibilita a obtenção de classes coesas e que evoluem mais fácil. Trata-se de pensar sempre em escrever classes que são abertas para extensão, mas fechadas para modificação — por isso, o nome “princípio do aberto/fechado”. A ideia é que as classes sejam abertas para extensão. Estender o compor- tamento delas deve ser fácil. Mas, ao mesmo tempo, elas devem ser fechadas para alteração — ou seja, elas não devem ser modificadas (ter seu código alterado) o tempo todo. De certa forma, deve-se permitir acrescentar funções e comportamentos de uma classe, sem alterar seu código base. Isso é possível por meio de herança, interface e composição. Princípios de projeto e modelagem SOLID6 Quando se estende uma função, incrementa-se sem alterar aquilo que está pronto. Já a alteração faz o oposto: para alterar algo, é necessário mexer no que já está pronto, e cada mudança traz a possibilidade de causar bugs na aplicação. Estes, muitas vezes, não são percebidos durante os testes, somente quando o sistema estiver em uso, gerando descontentamento e diminuindo a qualidade do software. Lembre-se de que a classe deve possibilitar extensão, e não modificações. Veja o exemplo da Figura 3, que apresenta uma estrutura de dados definindo o tipo de débito para preencher a classe Debito. A mesma foi desenvolvida com a linguagem Java, paradigma OO (orientada a objetos). No exemplo, a classe de débito valida o tipo de conta por meio da estrutura condicional if. Figura 3. Lista de dados TipoDebito e classe Debito. Ao analisar a classe Debito, podemos perceber que, caso surja outro tipo de conta, a classe Debito deverá ser modificada, acrescentando mais estruturas condicionais para validar o recebimento dos dados. Esse tipo de modificação poderia trazer riscos ao projeto, como os tipos de débitos pararem de funcionar corretamente. 7Princípios de projeto e modelagem SOLID Esse problema poderia ser facilmente resolvido se o princípio OCP fosse aplicado desde o início do projeto. Veja o exemplo da Figura 4, que apresenta uma abstração, em que a classe Debito representa a classe Pai, possuindo o método — a ação principal — e as classes filhas: DebitoContaCor- rente, DebitoContaPoupanca e DebitoContaInvestimento. Figura 4. Classe abstrata Debito e classes filhas. Princípios de projeto e modelagem SOLID8 Ao analisar a classe Debito, podemos perceber que a mesma é abstrata, o que significa que não será instanciada, mas servirá de modelo para suas classes filhas. As classes filhas sobrescrevem os métodos para realizarem a sua implementação; elas são conhecidas também por classes concretas. Por meio da Figura 4, percebe-se que qualquer modificação, como uma conta nova, não influenciará no funcionamento dos demais tipos de conta, pois são classes separadas e concretas. O tipo de débito em conta de investimento foi implementado sem necessidade de mudanças, usando apenas a extensão. Ou seja, o princípio OCP deixou o código mais limpo, de fácil manutenção e de acordo com o primeiro princípio SOLID, o princípio de responsabilidade única. Princípio da substituição de Liskov (LSP) No LSP, as classes derivadas devem ser substituíveis por suas classes bases e vice-versa. Isso significa que as classes filhas — derivadas — podem ser substituídas pela classe pai — superclasse —, e a superclasse também pode ser substituída por qualquer subclasse. Uma subclasse deve sobrescrever os métodos da superclasse, sem alterar a funcionalidade de que o cliente precisa. O uso de herança possibilita que as entidades do software possam ser extensíveis e reaproveitadas, evita duplicação de atributos e deixa o código mais coeso. Superclasse, classe pai ou classe base é a classe que concede as características a outra classe. Ela normalmente possui métodos abstratos para serem sobrescritos. Subclasse, classe filha ou classe derivada é a classe que herda atributos da classe pai. O LSP é visto como uma extensão do OCP, já que suas características são semelhantes. Uma das diferenças entre eles é que o OCP é centrado no aco- plamento de abstrações, enquanto o LSP, além do acoplamento de abstrações, é fortemente ligado a precondições e pós-condições do projeto. Para que o uso de herança seja adequado, o desenvolvedor deve analisar a todo momento as pré e pós-condições que estão definidas para a superclasse. 9Princípios de projeto e modelagem SOLID De acordo com Aniche (1998), toda classe ou método tem as suas pré e pós-condições. Por precondições entenda os dados que chegam nela, as restrições iniciais para que aquela classe ou método funcione corretamente. As pós-condições são o outro lado da moeda: o que aquele comportamento devolve? O exemplo da Figura 5 apresenta uma abstração, em que a classe Retan- gulo representa a superclasse, possuindo dois atributos — base e altura — e um método, e a subclasse Quadrado herda altura e largura. Figura 5. Superclasse Retangulo e subclasse Quadrado. Ao se analisar a classe Retangulo, percebe-se que há dois atributos: largura e altura. Porém, um quadrado não é um retângulo. Ao se fortalecer essa herança, o que vai acontecer com a base e a altura? Isso pode gerar problemas no resultado final. Esse problema poderia ser resolvido se o princípio LSP fosse aplicado desde o início do projeto. Veja o exemplo da Figura 6, que apresenta uma abstração, em que a classe FormaGeometrica representa a superclasse, possuindo apenas um método abstrato, e as duas subclasses, Retangulo e Quadrado, sobrescrevem o método calcularArea. Princípios de projeto e modelagem SOLID10 Figura 6. Superclasse FormaGeometrica e subclasses Retangulo e Quadrado. A solução para a herança apresentada no exemplo da Figura 5 foi a criação da classe abstrata; o método abstrato foi sobrescrito pelas classes Retangulo e Quadrado, e, assim, não foram mantidos métodos novos nas subclasses. Princípio da segregação de interfaces (ISP) O ISP estabelece que muitas interfaces específicas são melhores do que uma interface única geral. O quarto princípio SOLID se preocupa com a coesão de interfaces, visando à criação de poucos comportamentos, a fim de se manter e evoluir o projeto sem dificuldades. Como menciona Aniche (1998, p. 91): “Lembre-se de fugir de interfaces gordas. Elas têm baixo reúso, e quando o desenvolvedor não tem a experiência necessária para perceber e resolver o problema, ele acaba por complicar ainda mais o projeto de classes”. Vamos analisar o exemplo da Figura 7, que apresenta uma abstração,em que a classe Funcionario representa a superclasse, possuindo três atributos e três métodos, sendo dois deles abstratos; três subclasses herdam os atributos e sobrescrevem os métodos. 11Princípios de projeto e modelagem SOLID Figura 7. Superclasse Funcionario e subclasses Vendedor, Repre- sentante e AtendenteCaixa. Princípios de projeto e modelagem SOLID12 A Figura 7 apresenta um exemplo em Java em que a classe abstrata Fun- cionario possui o básico que todo o funcionário da empresa tem: nome, cargo e salário-base. Ela possui um método para salário-base e dois métodos abstratos. A subclasse Vendedor representa um funcionário, mas possui uma lógica própria para calcular o seu salário e sua comissão. A subclasse Representante não representa, exatamente, um funcionário, pois este profissional não recebe salário-base, mas recebe uma comissão sobre o total de suas vendas. Já a subclasse AtendenteCaixa representa um funcionário, porém, que não recebe comissão. Nesse exemplo, a herança não está sendo usada da forma mais correta, sendo que há subclasses que não são realmente especializações da superclasse, e existem classes que não necessitariam so- brescrever os métodos de todo o funcionário. Veja a seguir um exemplo correto, de acordo com o princípio ISP. A Figura 8 apresenta uma abstração em que a classe Funcionario representa a super- classe, possuindo três atributos e três métodos, sendo dois deles abstratos; três subclasses herdam os atributos e sobrescrevem os métodos. A Figura 8 apresenta um exemplo em Java com uso de interface e herança, em que existem duas interfaces, Assalariado e Comissionario, e uma classe abstrata Funcionario. Logo: � a classe Vendedor estende Funcionário e faz uso da interface Comissionario; � a classe Representante estende Funcionario e faz uso da in- terface Assalariado, e � a classe AtendenteCaixa estende Funcionario. Essa solução evita a criação errônea de uma herança e gera uma alta coesão ao código, com baixo acoplamento. 13Princípios de projeto e modelagem SOLID Figura 8. Interfaces Assalariado e Comissionario, superclasse Fun- cionario e subclasses Vendedor, Representante e AtendenteCaixa. Princípios de projeto e modelagem SOLID14 Princípio da inversão de dependências (DIP) O DIP sugere que os módulos dependam de abstrações, e não de implementa- ções. Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações. Para entender módulos de alto e baixo nível, suponha duas classes, Botao e Lampada, ambas concretas, conforme mostra a Figura 9. A classe Lampada tem métodos para ligar e desligar, e a classe Botao apresenta uma lógica para acionar os botões. Figura 9. Classes Botao e Lampada. Ao analisar o exemplo acima, percebe-se que a estrutura criada viola o princípio de inversão de dependência, já que Botao depende de uma classe concreta Lampada. O correto, segundo o DIP, seria o uso da abstração. 15Princípios de projeto e modelagem SOLID Veja a seguir um exemplo correto, de acordo com o DIP. A Figura 10 apresenta uma abstração, em que a classe Dispositivo representa a Inter- face, possuindo dois métodos, além da classe Lampada, a qual implementa Dispositivo e sobrescreve os métodos, e da classe Botão, que utiliza a abstração para controlar Dispositivo e cria a lógica para ligar e desligar. Figura 10. Interface Dispositivo, classe abstrata Lampada e classe Botao. A solução acima inverte a dependência de Botao em relação à classe Lam- pada, fazendo com que ambos agora dependam da abstração Dispositivo. Princípios de projeto e modelagem SOLID16 SOLID: padrões de arquitetura e padrões de projeto Todo projeto requer qualidade. Aplicar princípios SOLID nos projetos traz inúmeros benefícios, sendo um dos maiores o uso correto da programação orientada a objetos, evitando erros como: falta de padronização do código, duplicação de dados, classes com muitas responsabilidades e dificuldade de manutenção. Ao aplicar os princípios SOLID, obtém-se como resultado um código fácil de manter, coeso e de baixo acoplamento. Ficará mais fácil aplicar testes, reaproveitar classes e evoluir o sistema como um todo, facilitando também a aplicação dos padrões de arquitetura e dos padrões de projeto. Conhecer os padrões possibilita desenvolver soluções cada vez melhores e mais eficientes, que podem ser reutilizadas. O uso de padrões de projeto propicia a cons- trução de aplicações e ou estruturas de código de forma flexível e a documentação de soluções reaproveitáveis. Acesse o link a seguir e leia mais sobre os padrões de projetos e seus objetivos. https://qrgo.page.link/dHfTZ Um exemplo da facilidade de inserção do padrão é a inversão de depen- dência presente no design de vários padrões. Isso porque todos eles definem uma interface, para que não haja uma dependência forte de implementações. O padrão Bridge é um desses padrões. Outro exemplo é o padrão Composite, o qual segue o princípio ISP. Vários padrões oferecem uma interface para cliente, muitos deles baseados nos princípios SOLID, já que a aplicação dos mesmos facilita o uso dos padrões. Por exemplo, o padrão Adapter oferece uma nova interface, que adapta uma classe já existente às necessidades do cliente. 17Princípios de projeto e modelagem SOLID O uso de boas práticas de programação tem por finalidade justamente reduzir a com- plexidade do código e o acoplamento entre classes, separar responsabilidades e definir muito bem as relações entre elas, como forma de melhorar a qualidade interna do código-fonte. Para saber mais sobre o tema, leia o artigo de Mariana Azevedo, “Princípios SOLID: o que são e porque projetos devem utilizá-los” (2018), disponível no link a seguir. https://qrgo.page.link/ogcCd ANICHE, M. Orientação a objetos e SOLID para ninjas: projetando classes flexíveis. São Paulo: Casa do Código, 1998. (Série Caleum). AZEVEDO, M. Princípios S.O.L.I.D: o que são e porque projetos devem utilizá-los. Brasil, 2018. Disponível em: https://medium.com/@mari_azevedo/princ%C3%ADpios-s-o-l- -i-d-o-que-s%C3%A3o-e-porque-projetos-devem-utiliz%C3%A1-los-bf496b82b299. Acesso em: 9 dez. 2019. MARTIN, R. C. The interface segregation principle. [S. l.], 2009. Disponível em: http://www. objectmentor.com/resources/articles/isp.pdf. Acesso em: 9 dez. 2019. PRESSMAN, R.; MAXIM, B. Engenharia de software: uma abordagem profissional. 8. ed. São Paulo: AMGH, 2016. Leituras recomendadas ARAGÃO, T. Open closed principle. Brasil, 2011. Disponível em: https://medium.com/@tba- ragao/solid-i-s-p-interface-segregation-principle-c0b25d7dccf9. Acesso em: 9 dez. 2019. DEVMEDIA. Open closed principle. Rio de Janeiro, 2010. Disponível em: https://www. devmedia.com.br/open-closed-principle/18970. Acesso em: 9 dez. 2019. HORSTMANN, C. Padrões e projetos orientados a objetos. 2. ed. Porto Alegre: Bookman, 2007. LARMAN, C. Utilizando UML e padrões: uma introdução à análise e ao projeto orientado a objetos e ao desenvolvimento iterativo. 3. ed. Porto Alegre: Bookman, 2007. MAGALHÃES, I. L.; PINHEIRO, W. B. Gerenciamento de serviços de TI na prática: uma abordagem com base na ITIL. São Paulo: Novatec, 2007. Princípios de projeto e modelagem SOLID18 MARTIN, R. C. The dependency inversion principle. [S. l.], 2009. Disponível em: http://www. objectmentor.com/resources/articles/dip.pdf. Acesso em: 9 dez. 2019. PRESSMAN, R. S. Engenharia de software: uma abordagem profissional. 7. ed. São Paulo: AMGH, 2011. Os links para sites da Web fornecidos neste capítulo foram todos testados, e seu fun- cionamento foi comprovado no momento da publicação do material. No entanto, a rede é extremamente dinâmica; suas páginas estão constantemente mudando de local e conteúdo. Assim, os editores declaram não ter qualquer responsabilidade sobre qualidade, precisão ou integralidade das informações referidas em tais links. 19Princípios de projeto e modelagem SOLID
Compartilhar