Abílio Azevedo.

SOLID - Design Principles

Cover Image for SOLID - Design Principles
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:


More posts

Cover Image for The Phychology of Money

The Phychology of Money

Morgan Housel offers valuable insights on financial management and decision-making. The author emphasizes that financial success depends more on behavior than on intelligence or technical knowledge. Housel highlights the importance of long-term vision and resilience in the face of market volatility, encouraging us to focus on sustainability rather than short-term gains.

Cover Image for Bellsant

Bellsant

I've been at the forefront of developing a cutting-edge health and wellness app. Our tech stack combines React Native for cross-platform mobile development with a serverless NodeJS backend, leveraging AWS Lambda for scalability and cost-efficiency.

Abílio Azevedo
Abílio Azevedo

NewsLetter

I will send the content posted here. No Spam =)

Experienced Software Engineer with degree in Electrical Engineering with over 10 years of hands-on expertise in building robust and scalable mobile, web and backend applications across various projects mainly in the fintech sector. Mobile (React Native), Web (React and Next.JS) and Backend (Node.JS, PHP and DJANGO). My goal is to create products that add value to people. - © 2024, Abílio Azevedo