Abílio Azevedo.

SOLID - Princípios de Desenvolvimento

Cover Image for SOLID - Princípios de Desenvolvimento
Abílio Azevedo
Abílio Azevedo
  • A letra “S” se refere a Single Responsibility Principle (Princípio da Responsabilidade Única),
  • A letra “O” diz respeito a Open-Closed Principle (Princípio de Aberto-Fechado),
  • A letra “L” corresponde ao termo Liskov Substitution Principle (Princípio da Substituição de Liskov),
  • A letra “I” remete ao termo Interface Segregation Principle (Princípio da Segregação de Interface),
  • A letra “D” é referente a Dependency Inversion Principle (Princípio da Inversão de Dependência).

S - Princípio da Responsabilidade Única (SRP)

O Princípio da Responsabilidade Única diz que “Uma classe deve ter somente uma única razão para ser alterada, ou em outras palavras, uma classe deve possuir responsabilidade única”.

O princípio pode ser aplicado a funções também. Mas afinal, o que é uma responsabilidade e como saber se uma classe ou função possui ou não somente uma?

Uma responsabilidade pode ser considerada como um papel que uma classe ou função é responsável por executar, mas uma melhor definição, quando se tratando desse princípio, seria que uma responsabilidade é, basicamente, uma razão para mudar.

Isso quer dizer que se for possível pensar em mais de um motivo para alterar uma determinada classe, é porque essa classe está assumindo mais de uma responsabilidade.

Outra forma de saber se uma função faz mais de "uma coisa" é se você pode extrair outra função dela a partir de seu nome que não seja apenas uma reformulação de sua implementação.

O exemplo a seguir é de uma classe chamada "Car" que deveria representar o modelo de carro mas possui alguns serviços também. Nesse caso é melhor separar as classes. Classes e funções que respeitam o princípio da responsabilidade única são mais legíveis e testáveis

Embedded content: https://gist.github.com/kibolho/d00eccbe0d47b0c151dfee012894cebe

O - Princípio Aberto-Fechado (OCP)

O Princípio do Aberto-Fechado diz que “Entidades de software (classes, módulos, funções, etc) devem ser abertos para extensão, porém fechados para modificação”. 

Por vezes, estendemos as funcionalidades de nossas entidades e como estas estão acopladas a outras entidades acabamos tendo que efetuar modificações.

Um exemplo disso é quando temos uma estrutura de "switch case" que para cada tipo efetuamos um chamada da classe recebida. A cada nova classe e implementação precisamos adicionar um novo case. Isso infringe o princípio.

Podemos resolver isso implementando um protocolo/interface para essas classes para garantir que todas tenham o método a ser chamado na estrutura de switch. Dessa forma não precisamos mais usar a estrutura de switch e modificá-la sempre que um novo tipo de classe for adicionado.

Embedded content: https://gist.github.com/kibolho/720f62fdb8b0950f7c7929b4c4fb6a19

L - Princípio de Substituição de Liskov (LSP)

A definição matemática original, de Barbara Liskov para esse princípio é  Se para cada objeto o1 de tipo S, existe um objeto o2 de tipo T, de modo que para todos programas P definidos em termos de T, o comportamento de P é inalterado quando o1 é substituído por o2 , então S é um subtipo de T.

HAM???

Vou tentar explicar de uma forma melhor: O princípio define que os objetos de uma superclasse devem ser substituídos por objetos de suas subclasses sem quebrar a aplicação. Classes filhas nunca deveriam infringir as definições de tipo da classe pai.

No exemplo abaixo, podemos ver que o motorista consegue dirigir o veículo sem saber o tipo de veículo, somente sabendo as funções básicas. Embedded content: https://gist.github.com/kibolho/b551cdee4527d7d8296086d501c258da

I - Princípio de Segregação de Interface (ISP)

Como o nome sugere, esse princípio diz que devemos segregar as interfaces, ou seja, separá-las em várias.

Uncle Bob diz em seu artigo que os "clientes" não devem ser forçados a depender de interfaces que eles não utilizam.

Em primeiro lugar, um termo comumente usado pelo Uncle Bob em seu artigo, que nos ajudará a tornar as coisas mais claras, é “Fat Interfaces”. Traduzindo ao pé da letra, interfaces gordas. Esse tipo de interface gera alguns problemas, como refatoração desnecessária e necessidade de recompilar e retestar quando mudanças são necessárias.

Isso acontece porque esse tipo de interface acaba gerando acoplamentos desnecessários e, quando uma mudança em uma interface gorda é necessária, todas as classes que a implementam terão que ser compiladas e testadas novamente. Dependendo de quão complexo e grande é o software de que estamos falando, isso pode tomar uma grande quantidade de tempo e ser doloroso.

Além disso, implementar interfaces gordas também pode dificultar a compreensão e a testabilidade do seu software. Em termos de compreensão, porque as coisas provavelmente não farão sentido semanticamente. No caso de testabilidade, teremos que criar “mocks” e “spies” maiores, que terão que implementar todos os métodos desnecessários e não utilizados exigidos por esta interface gorda.

No caso abaixo, vemos que nem todas as aves sabem voar e por isso o ideal é criar tipos mais específicos e aplicá-los separadamente.

Embedded content: https://gist.github.com/kibolho/3d2efac77adfd2bf662c97805b9d8cd5

D - O princípio de inversão de dependência (DIP)

  • 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.
  • Com isso, qualquer módulo que usa apenas inversão de dependência pode ser testado na unidade, uma vez que suas dependências podem ser substituídas por mocks.

No caso abaixo, temos um serviço diretamente dependente da classe HttpClient, criando acoplamento com a lib do axios. A melhor forma nesse caso é implementar uma injeção de dependência por meio de uma interface para desacoplar a classe HttpClient. Embedded content: https://gist.github.com/kibolho/bceadfe823de7fb440bef41c77428081

Bad Design é quando temos estruturas dependendo de outras estruturas, em vez de depender de abstrações ou interfaces, gerando:

  • Rigidez: É difícil alterá-lo, porque possíveis alterações afetam outras partes deste software.
  • Fragilidade: É difícil implementar modificações, porque possíveis alterações quebram outras partes deste software.
  • Imobilidade ou acoplamento: é realmente difícil reutilizar ou extrair algum pedaço de código porque tudo está totalmente acoplado e, se você precisar fazer isso, provavelmente terá que realizar uma enorme refatoração.

Embedded content: https://docs.google.com/presentation/d/1on5CLXxHCrsjPy95h5Pr_ssKTso3JSik2juL9SpS8Ro/edit?usp=sharing

FONTES:


Mais posts

Cover Image for Documentos Técnicos

Documentos Técnicos

Aprenda a importância vital da documentação técnica abrangente para projetos de software em crescimento. Descubra as melhores práticas, como Requests for Comments (RFCs) e Architectural Decision Records (ADRs), que promovem transparência, colaboração e registro de decisões arquiteturais. Explore ferramentas poderosas como wiki.js e Backstage para criar centros de documentação eficazes. Mantenha seu projeto organizado, compreensível e sustentável com essa abordagem à documentação técnica.

Abílio Azevedo
Abílio Azevedo
Cover Image for Superlógica - BFF para o Gruvi

Superlógica - BFF para o Gruvi

Construindo um BFF (Backend for Frontend) para o SuperApp Gruvi que tem mais de 120 mil usuários ativos e milhões de possíveis usuários para disponibilizar no ecossistema Superlogica.

Abílio Azevedo
Abílio Azevedo

NewsLetter

Eu enviarei o conteúdo postado aqui no blog. Sem Spam =)

Engenheiro de software experiente, formado em Engenharia Elétrica, com mais de 10 anos de experiência prática na construção de aplicativos móveis, web e back-end robustos e escaláveis em vários projetos, principalmente no setor de fintech. Mobile (React Native), Web (React e Next.JS) e Backend (Node.JS, PHP e DJANGO). Meu objetivo é criar produtos que agreguem valor às pessoas. - © 2024, Abílio Azevedo