# Princípios S.O.L.I.D **Objetivo:** Criar código compreensível, legível e testável no qual diversos desenvolvedores possam trabalhar de modo colaborativo.​ * SOLID são cinco princípios do design de classes orientado a objetos. Eles são um conjunto de regras e práticas recomendadas a serem seguidas na criação de uma estrutura de classe; * Esses cinco princípios nos ajudam a entender a necessidade de determinados padrões de projetos e arquitetura de software em geral.​ ## Um pouco de história A história da origem do SOLID começa ao final da década de 1980 enquanto Robert C. Martin (conhecido como Uncle Bob) discutia princípios de design de software com outros usuários da USENET (uma espécie de Facebook da época) com o objetivo de catalogar os mais importantes. Essa catalogação estabilizou-se somente 20 anos depois, em 2000.​ ## SRP - Single Responsibility principle A classe deve ter uma, e apenas uma, razão pra mudar. Analisando esse conceito dá pra perceber que o SRP tem total ligação com o conceito de coesão, uma classe coesa é aquela que possui apenas uma responsabilidade, ela não toma conta de mais de um conceito no sistema, possui menos código e seu reuso é maior. Para ajudar a identificar falta de coesão em suas classes a dica é procurar por classes que: * Possuem muitos métodos diferentes; * São modificadas com frequência; * Não param de crescer. Atente para esses fatores e talvez você identifique uma oportunidade para aplicar este princípio. posteriormente o Uncle BOB redefiniu este princípio no intuito de esclarecer o significado de "responsabilidade", então a definição ficou assim: - Um módulo deve ser responsável por apenas um ator. **Ator:** É um tomador de decisões que define o comportamento desejado de uma parte específica do software. Entender que muitas vezes as features dos nossos sistemas são definidos por diferentes atores nos ajuda a evitar problemas em nossos códigos como: **Duplicação acidental:** acontece quando usamos funções compartilhadas para evitar duplicidade de código, quando na verdade deveríamos criar duas implementações. Por exemplo, imagine uma classe produto que possui duas funções para o cálculo de preço: ![](https://i.imgur.com/3YHDVEp.jpg) Mas essas funções compartilham um mesmo cálculo de desconto. ![](https://i.imgur.com/1IdTVHv.jpg) Posteriormente o percentual de desconto para business sofreu uma alteração, como ele é definido por um outro departamento, ao alterar a função **applyDiscount()** acabamos duplicando a alteração. Por termos funções gerenciadas por dois atores distintos o ideal é não compartilhar funções, pois estes comportamentos mudam por razões diferentes. Um outro ponto importante ao construirmos softwares é evitar a **propagação de alterações**, que é quando você está alterando um comportamento e precisa realizar a mesma mudança em diversos pontos diferentes. Em sistemas mal projetados esses pontos de alteração são implícitos e você precisa buscá-los manualmente, é necessário encapsular melhor os comportamentos para que a mudança, quando feita em determinado ponto, seja propagada naturalmente pelo sistema. Algumas propagações de alterações são inevitáveis, mas não representam problema, às vezes são impostas pela arquitetura, porém o problema são as **dependências implícitas**. **Pensando um pouco mais sobre Coesão, será que os controllers quebram o conceito?** Não! o objetivo dos controllers numa arquitetura MVC ou mesmo no Clean Arquitecture é coordenar o processo, cada classe utilizada ali tem uma única responsabilidade e faz a sua parte, as únicas regras de negócio que o controller contém são regras de visualização, exemplo: se “isso” acontecer então exiba “aquilo”. um erro muito comum na criação de controllers é a **feature envy** (classes que invejam outras). **Exemplo de envy feature:** Imagine essa função como parte de um Controller, mas perceba que tudo o que ela faz é chamar funções da classe contrato, executar verificações com base em informações da classe contrato para então realizar ações que estão definidas na classe contrato, ou seja, há uma "inveja de recursos aqui", não é interessante criar classes que existam apenas para executar ações que estão definidas em uma outra classe. ![](https://i.imgur.com/htqyDlV.jpg) Outro problema muito comum de código mal encapsulado, que encontramos principalmente em códigos legados, é a **intimidade inapropriada**: quando uma classe qualquer sabe demais sobre como funciona a outra classe. ![](https://i.imgur.com/qMhYArk.jpg) Perceba que a classe que contém esse código está usando o valor da classe NotaFiscal para realizar um cálculo com base em mais um dado também da classe Nota Fiscal, quem deve saber sobre a regra de negócio da NotaFiscal é ela mesma. Aqui podemos usar o **Tell, Don’t ask (Diga, não pergunte)** para corrigir esse problema, no exemplo acima a primeira coisa que fazemos é uma pergunta (if) para o objeto, de acordo com o retorno damos uma ordem para o objeto. a ideia do Tell, Don’t ask é que devemos primeiro dizer ao objeto o que ele tem que fazer, e não perguntar algo a ele pra poder decidir. Refatorando ficaria assim: ![](https://i.imgur.com/DF7QDY8.jpg) **Dicas:** É difícil definir o que é responsabilidade pode soar como algo bem subjetivo, mas dois comportamentos pertencem ao mesmo conceito/responsabilidade se ambos mudam juntos. atente para os atores da aplicação e na definição dos nomes das classes tente descrever tudo o que ela faz, se o nome for muito grande e descrever diferentes comportamentos, talvez seja um sinal de que nossa classe está com mais de uma responsabilidade. No livro Clean Archtecture aponta que a estrutura de nossos sistemas, descrição das funções e classes, organização dos diretórios e arquivos devem deixar claro sobre sua razão de existir. isso é chamado de **arquitetura gritante** e ela também contribui para a definição de códigos coesos. ## OCP - Open-Closed Principle; As classes devem ser abertas para extensão, fechadas para modificação. Sistemas devem ser projetados de modo a permitirem que um comportamento mude pela adição de um novo código, em vez da alteração do código existente a vantagem é que isso reduz o esforço para entender a base de código atual e o risco de quebrar um comportamento anterior. Ao modificarmos o código, adicionando condições, por exemplo, aumentamos a sua complexidade e assim reduzimos a legibilidade e a sua testabilidade. Imagine uma classe pagamento que possui funções para as formas de pagamento: ![](https://i.imgur.com/bSBkcSu.png) Nesta situação, para incluir uma nova forma de pagamento seria necessário alterar a classe que já existe, já foi testada e está em produção, na imagem acima estamos descrevendo essa situação com funções bem simples, que apenas imprimem a forma de pagamento, para não poluir tanto o texto e para focarmos no conceito, mas imagine funções bem mais complexas e repletas de validações e condicionais, e então você precisa adicionar uma nova forma de pagamento. A ideia é que ao utilizar o OCP, tenhamos classes estruturadas de tal forma que a adição de novas features não implicam em alterações na classe pagamento. Para isso transformamos a classe pagamento em uma classe abstrata que será nosso contrato, e ela possui a função pagar, cada forma de pagamento deve deve ser uma classe concreta que implementa essa "interface". ![](https://i.imgur.com/P1eZFMF.png) No Exemplo acima foi usado **implements** e **extends** apenas para reforçar a diferença entre elas em relação ao atributo valor. Se precisarmos adicionar novas formas de pagamento, basta criar uma nova classe que também implementa o nosso contrato. ![](https://i.imgur.com/m0MPnhF.png) **Dicas:** Classes abertas são aquelas que deixam explícitas as suas dependências, podemos mudar as implementações concretas que são passadas para ela (por meio de inversão de dependência que veremos mais à frente). > *“Projetar código para ser extensível sem modificar o artefato é a razão mais fundamental pela qual estudamos a arquitetura de software.” (Robert C. Martin - Uncle BOB)* ## LSP - Liskov Substitution Principle Os subtipos devem ser substituíveis pelo seu tipo pai sem quebrar o sistema. Em sistemas criados a partir de partes intercambiáveis, as partes devem aderir a um contrato que permite que elas sejam substituíveis umas pelas outras. Quando uma classe filha não pode executar as mesmas ações que sua classe pai , isso pode causar bugs. Em linguagens fortemente tipadas como dart, swift e kotlin não é possível quebrar a hierarquia no nível de código, pois o código não compilaria, porém podemos quebrar logicamente a hierarquia e o compilador não pode detectar. - Se uma subclasse tiver regras de validação de entrada mais rígidas e colocarmos no lugar de uma implementação menos restritiva, a entrada causará erros. - Os valores de retorno da função substituída devem estar em conformidade com as mesmas regras que a função da superclasse, mas para valores de retorno você pode optar por retornar um pequeno subconjunto do que o pai retorna. Esse princípio serve bem para definirmos melhor a hierarquia de nossas classes, fazendo com que as classes filhas sigam algumas restrições ao implementar os comportamentos da classe pai, desta forma podemos evitar erros se por algum motivo um objeto for substituído por outro, em algum momento do código. No exemplo abaixo, temos a classe Conta com 3 funções e temos as classes ContaCorrente e ContaPoupanca que implementam a classe Conta, porém a ContaPoupança não realiza empréstimo, temos um problema pois a classe filha está sendo obrigada a implementar uma função da qual não utiliza e uma substituição dos objetos em tempo de execução resultaria numa exception ao chamar o método realizarEmprestimo(). ![](https://i.imgur.com/nqQgJil.png) Aqui o objeto ContaCorrente foi substituído pela ContaPoupanca: ![](https://i.imgur.com/cNfRgAZ.png) Saída: ![](https://i.imgur.com/qjxQmHM.png) O próximo princípio traz técnicas para reestruturar esse código e permitir que as classes sejam substituíveis. **Dicas:** *Validações de entrada* não podem ser mais restritivas, só podem ser mais flexíveis, enquanto que *validações de saída* só podem ser mais restritivas, não podem ser mais flexíveis. ## ISP - Interface Segregation Principle Os clientes não devem ser forçados a depender de métodos que não usam; Este princípio visa dividir um conjunto de ações em conjuntos menores para que uma classe execute apenas o conjunto de ações de que necessita. Depender de métodos que não serão usados é um desperdício e pode produzir bugs. A vantagem de usar o ISP é que quando temos interfaces coesas e evitamos **fat interfaces**, temos maior reuso e elas tendem a ser mais estáveis. Refatorando o exemplo anterior, a estrutura de código ficaria assim: ![](https://i.imgur.com/bpaFLlh.png) Assim cada classe utiliza apenas as funções que precisa e se houvesse uma substituição de objetos, não seria possível acessar o método realizarEmprestimo() a partir da classe ContaPoupanca, por exemplo. **Dicas:** Para identificarmos onde utilizar este princípio é importante atentarmos para as fat interfaces (aquelas interfaces que contém muitas responsabilidades diferentes), outro ponto que nos ajuda a perceber que precisamos segregar nossas interfaces são alguns **code smells** específicos como: * **Bulky interfaces:** São as interfaces cheias de funcionalidades, provavelmente alguma classe que implementa ela não utiliza uma dessas funções. * **Unused dependencies:** * **Functions throwing exceptions:** * **Empty overridden functions:** Muitas vezes ao implementar uma classe você pode gerar as assinaturas das funções para sobrescrever deixando-as vazias, isto pode indicar que existem classes com funções que não precisam. > *“Code smells são indicações superficiais que normalmente correspondem a um problema de sistema mais profundo” (Martin Fowler)* ## DIP - Dependency Inversion Principle Depender de abstrações e não de classes concretas. Módulos de alto nível não devem depender de módulos de baixo nível, ambos devem depender de abstrações. Abstrações não devem depender de detalhes, detalhes devem depender de abstrações. * Programar voltado a interface ajuda a flexibilizar; * Uma classe com muitas dependências torna-se frágil, fácil de quebrar; * Quando uma classe tem muitas dependências, todas elas podem propagar problemas para a classe principal. **Dicas:** sempre que uma classe for depender de outra, deve depender de outro módulo **mais estável** do que ela mesma, precisamos buscar acoplar a classes que mudam pouco. **Estabilidade:** Quando a classe muda muito pouco ou com pouca frequência e portanto ela propaga poucas mudanças para a classe principal; **Acoplamento:** Se seguirmos o SRP a ideia é que nossas classes estejam coesas, em nosso sistema teremos várias delas e é preciso agrupar e gerenciar os comportamentos, A ideia é que façamos isso evitando ao máximo as dependências. * **Dependências lógicas:** Para vermos o acoplamento basta verificar as dependências, estruturalmente dá pra fazer isso até olhando os imports da classe. e às vezes é definição da própria arquitetura utilizada (MVC, Clean Architecture, …) e nesses casos quando vc cria um comportamento deve propagar nos outros arquivos, o problema é quando está implícito, quando existe acoplamento e não sabemos o motivo, quando um muda o outro muda junto. Nesse exemplo a classe GeradorDeNotaFiscal possui 3 dependências: ![](https://i.imgur.com/t1etdjV.jpg) Aplicando a inversão de dependência poderíamos refatorar dessa forma: ![](https://i.imgur.com/AcEqVn1.jpg) O uso da inversão de dependências nos possibilita o uso de mock objects em nossos testes unitários. **Mock Objects:** são objetos falsos que simulam o comportamento de outros objetos, se não usássemos precisaríamos instanciar concretamente todas as dependências da classe que estamos testando, seria mais trabalhoso e se o teste falhar não saberíamos de imediato se o problema era na classe principal ou em suas dependências, quando "mockamos" dependências se o teste falhar sabemos que o problema está na classe que estamos testando. Neste exemplo, passamos a classe de pagamento pelo construtor, e dependendo do objeto que for passado, o método realizarPagamento() deve se comportar de maneira diferente, sendo possível dessa forma passar um mock para a classe ContaCorrente no intuito de realizarmos testes. ![](https://i.imgur.com/Dg2hSZQ.png) ## Saiba mais! ### Dicas de Livros: ![](https://i.imgur.com/GIsChii.png) ### Artigos: https://medium.com/contexto-delimitado/os-princ%C3%ADpios-solid-34b136f507bb​ https://medium.flutterdevs.com/s-o-l-i-d-principles-in-dart-e6c0c8d1f8f1​ https://medium.com/backticks-tildes/the-s-o-l-i-d-principles-in-pictures-b34ce2f1e898​ https://dev.to/elianmortega/vol-2-solid-rules-in-dart-2e6m​ https://medium.com/itnext/how-to-make-yours-rock-solid-66ea50bce31f ### Vídeos: https://www.youtube.com/watch?v=GtGjo7lX7CI&list=PLRpTFz5_57ctOsMqglte1W_xa-rek-8G9 https://www.youtube.com/watch?v=rtmFCcjEgEw&t=4s