Guia de S.O.L.I.D. com Java
Tags: java, learning, beginners, oop
Published: 2024-04-29 21:03:52

SOLID é importante para qualquer linguagem de programação que adote o paradigma de orientação a objetos, eles facilitam o processo de desenvolvimento, como por exemplo na manutenção e escalabilidade do código, visando boas práticas e melhorando a vida útil de nós programadores. Este post é focado para quem não sabe sobre o significado de SOLID e também para quem sabe mas procura um exemplo na linguagem Java, não irei focar no paradigma de orientação a objetos pois não é foco, porém, se você não conhece, é importante que visite este [link](https://www.alura.com.br/artigos/poo-programacao-orientada-a-objetos) para que compreenda melhor este post.

## O que é SOLID?
SOLID é um acrônimo idealizado por [Micheal Feathers](https://michaelfeathers.silvrback.com/) que unificou os princípios da orientação a objetos apresentados inicialmente por Robert C Martin, mais conhecido como ["Uncle Bob"](https://en.wikipedia.org/wiki/Robert_C._Martin) em seu artigo chamado ["The principles of OoD"](http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod) de 1995, a sigla significa:


![Imagem com a sigla SOLID e seus significados em inglês](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hte4b0so3oft69f6ei3t.png)

Mais abaixo irei explicar com detalhes o que cada letra dessas significa tanto na teoria quanto na prática com Java, explorando exemplos também com imagens.
 
## S – Single Responsability Principle (Princípio da Responsabilidade Única)
Este princípio diz que cada método, função ou classe, deve ter uma responsabilidade única, ou seja, deve ter um único objetivo.


![Imagem que representa o princípio de responsabilidade única, mostrando um desenvolvedor fullstack se desmembrando em um front-end e um back-end](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f1k5gcrspxvnt1dqhwv9.png)

Como a imagem acima demonstra, neste princípio, cada um tem uma única função, sendo uma má prática alguma classe ou método fazer mais de uma coisa ao mesmo tempo, vamos ver o exemplo em código, imaginando um cenário de um método que gera senhas, verifica e salva no banco de dados:
```java
public String geraVerificaESalvaSenha() {
  // Implementação do código...
}
```
## Problemática
Apesar de ter um nome que diz exatamente o que o método faz, como uma boa prática de código limpo, o método faz muitas coisas ao mesmo tempo, se alguma dessas etapas estiverem com bug, fica mais difícil de identificar, é importante que separe tudo para que não se tenha dupla ou mais funções.

## Solução
Separar cada etapa do método acima em diferentes métodos.
```java
public String gerarSenha() {
  // Implementação do código...
}

public void verificarSenha(String senha) {
  // Implementação do código...
}

public void salvarSenha(String senha) {
  // Implementação do código...
}

```

Com isso, é mais fácil de identificar um possível erro que venha a acontecer, podendo até mais facilmente no futuro separar estes métodos para diferentes classes.

Uma forma de identificar se um método está fazendo mais de uma função é se atentar ao seu nome, como no exemplo acima, para classes, é importante prestar atenção se seus métodos implementados condizem com sua abstração, como por exemplo:
```java
public class Copo {
 
  public void notificarEstado() {
     // Implementação do código... 
  }

  public void limpar() {
    // Implementação do código...
  }

  public void encher() {
    // Implementação do código...
  }

  public void esvaziar() {
    // Implementação do código...
  }
 
}
```
## Problemática
Podemos ver no exemplo acima que temos métodos que não precisam estar implementados nesta classe, como por exemplo, _notificarEstado_ para saber se um copo está vazio ou cheio ou o método limpar que serve para limpar o copo, estes métodos não fazem sentido na abstração de um Copo, pois abragem muito mais coisas.

## Solução

Delegar os métodos que não fazem parte de uma abstração para uma outra que faça mais sentido, como podemos ver abaixo:

```java
public class Notificação {
  public void notificar() {
     // Implementação do código... 
  }
}
```

```java
public class LavaLoucas {
  public void limpar() {
     // Implementação do código... 
  }
}
```

```java
public class Copo {
  public void encher() {
    // Implementação do código...
  }

  public void esvaziar() {
    // Implementação do código...
  }
}
```

Por mais que as classes sejam bem pequenas inicialmente, é mais fácil para expandir e podermos implementar outras classes que possam utilizar da notificação e de serem limpos também, assim a classe Copo fica responsável somente por aquilo que faz sentido para sua abstração.

## O – Open-Closed Principle (Princípio Aberto-Fechado)
Este princípio diz que suas classes e métodos/funções devem estar abertos para extensões e fechados para modificações, ou seja, se você quer realizar adições as suas classes/métodos, não é recomendável que se mexa em seu comportamento, isso evita que gere possíveis bugs a cada código novo adicionado.


![Imagem que mostra dois emojis de contrato de clt e de estágio se tornando apenas um contrato que é o de funcionário.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w8uqx0ladvnkou4xb9v8.png)

A imagem acima representa o nosso exemplo que veremos logo abaixo. 

```java
public class CLT {
  public int salario() {
    // Implementação do código...
  }
}
```
```java
public class Estagio {
  public int bolsaAuxilio() {
    // Implementação do código...
  }
}
```
```java
public class FolhaDePagamento {
  private int saldo;  

  public int calcular(Object funcionario) {
    if (funcionario instanceof CLT) {
      this.saldo = ((CLT) funcionario).salario();;
    } else if (funcionario instanceof Estagio) {
      this.saldo = ((Estagio) funcionario).bolsaAuxilio();
    }
  }
}
```
## Problemática
Neste exemplo acima, a medida que formos adicionando novos tipos de funcionários, novas verificações seriam necessárias e estaríamos mudando o comportamento do método.

## Solução
Podemos observar que tanto o funcionário CLT quanto o estagiário, recebem uma remuneração, sendo assim, podemos simplificar os dois como uma interface que atenda os dois, deste jeito:

```java
public interface Funcionario {
  int remuneracao();
}
```
```java
public class CLT implements Funcionario {
  
  @Override
  public int remuneracao() {
    // Implementação do código...
  }
}
```
```java
public class Estagio implements Funcionario {
  
  @Override
  public int remuneracao() {
    // Implementação do código...
  }
}
```
```java
public class FolhaDePagamento {
  private int saldo;

  public int calcular(Funcionario funcionario) {
    this.saldo = funcionario.remuneracao();
  }
}
```

Com a criação desta simples interface, foi possível que simplificássemos nosso método calcular podendo assim receber qualquer tipo de funcionário, basta que implemente a interface Funcionário criando o método de remuneração, sendo assim, não precisa ser alterado caso queiramos adicionar mais tipos de funcionários.

## L – Liskov Substitution Principle (Princípio da Substituição de Liskov)

A ideia deste princípio surge de uma cientista americana chamada Barbara Liskov que diz que as classes derivadas devem ser substituíveis pelas suas classes bases.

Esta ideia é a mesma da herança, a classe-filha deve ter/fazer tudo que a sua classe-mãe tem/faz, podendo ser substituída, não criando bugs por não ter implementado.

![Imagem que descreve o Princípio da Substituição de Liskov usando de exemplo um Midia player que tem como classes filhas Mp3Player e Mp4Player](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b5o8tk5ikcpj03uayc9s.png)

Para este princípio, trouxe um exemplo de um mediaplayer que tem como objetivo reproduzir qualquer tipo de mídia, um áudio, vídeo, etc.
```java
public class MediaPlayer {
  public void reprodruzir() {
    System.out.println("Reproduzindo mídia...");
  }
}
```
```java
public class Mp3Player {
  public void reprodruzir() {
    System.out.println("Reproduzindo som...");
  }
}
```
```java
public class Mp4Player {
  public void reprodruzir() {
    System.out.println("Reproduzindo vídeo...");
  }
}
```
Veja que podemos alternar entre as classes dos players de mp3 e mp4 que irão fazer a mesma função que sua classe-mãe e podem ser substituídas pela sua classe base.

## I – Interface Segregation Principle (Princípio da Segregação de Interface)

O princípio da segregação de interface diz que uma classe filha não pode ser forçada a implementar métodos dos quais ela não irá utilizar.

![Imagem onde temos 3 classes sendo representadas, trabalho, robô e humano. Temos 2 versões, a errada, onde todos os métodos de trabalho estão sendo implementados em ambas classes filhas e a versão correta, onde as classes filhas tem métodos especifícos que fazem sentido.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ilxx3008n5rvngs2abzz.png)

Podemos ver mais abaixo, um exemplo onde temos um robô e um humano que irão executar um trabalho, classe essa que foi definida para que ambos executem estas tarefas, já que são suas filhas.

```java
public interface Trabalho {
  void comecarATrabalhar();

  void almocar();

  void carregarBateria();

  void terminarTrabalho();
}
```
```java
public class Robo implements Trabalho {
  @Override
  public void comecarATrabalhar() {
    // Implementação do código...
  }

  @Override
  public void almocar() {
    return null;
  }

  @Override
  public void carregarBateria() {
    // Implementação do código...
  }

  @Override
  public void terminarTrabalho() {
    // Implementação do código...
  }
}
```
```java
public class Humano extends Trabalho {
  @Override
  public void comecarATrabalhar() {
    // Implementação do código...
  }

  @Override
  public void almocar() {
    // Implementação do código...
  }

  @Override
  public void carregarBateria() {
    return null;
  }

  @Override
  public void terminarTrabalho() {
    // Implementação do código...
  }
}
```
## Problemática
Existem coisas que não são feitas por humanos como carregar a bateria e nem um robô que iria almoçar, ou seja, não é recomendado que se tenha métodos que retornem nulo pois foram forçados a serem implementados.

## Solução
Basta que determine os métodos que apenas serão usados por todos e deixem os mais especifícos para que sejam exclusivos de suas implementações, como podemos ver abaixo:

```java
public class Trabalho {
   public void comecarATrabalhar() {
      // Implementação de código...
   }
   
   public void terminarTrabalho() {
      // Implementação de código...
   }
}
```
```java
public class Robo extends Trabalho {
   public void carregarBateria() {
     // Implementação de código...
   }
}
```
```java
public class Humano extends Trabalho {
    public void almocar() {
      // Implementação de código...
    }
}
```

## D – Dependecy Inversion Principle (Princípio da Inversão de Dependência)

Este princípio se baseia na ideia de que abstrações não devem depender de detalhes e sim os detalhes que devem depender de abstrações.

Esta definição pode ser reescrita de outra forma, como, módulos de alto nível não devem de módulos de baixo de baixo nível, ambos devem depender de abstrações. 

Caso não tenha ficado claro ainda, abaixo temos um exemplo.

![Nesta imagem temos duas versões, uma certa e outra errada. Na errada, temos a classe EmailService dependendo de JavaMailSender e na versão correta, temos o EmailService que depende da interface EmailSenderGateway que é implementada por JavaMailSender.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7qbzs3pssk33573o8rko.png)

Neste exemplo, temos um serviço que envia e-mails utilizando uma biblioteca chamada JavaMailSender.

```java
public class EmailService {
  private final JavaMailSender mailSender;

  public EmailService(JavaMailSender mailSender) {
      this.mailSender = mailSender;
  }

  public void enviarEmail(String emailTo, String subject, String body) throws MailException {
      SimpleMailMessage message = new SimpleMailMessage();
      message.setTo(emailTo);
      message.setSubject(subject);
      message.setText(body);

      mailSender.send(message);
  }
}
```
## Problemática

O problema desse código está na dependência na biblioteca JavaMailSender, imagine que a biblioteca seja descontinuada ou qualquer outro tipo de problema, precisáriamos reescrever toda a classe para trocar esta biblioteca, trocar para o Amazon SES, mailgun.

## Solução

Basta adicionar uma interface que faz o JavaMailSender conversar com o serviço criando o método por ele necessário, agora se quisermos trocar para uma implementação utilizando Amazon SES ou mailgun, bastaria criar outra classe que implementasse esta interface chamada EmailSenderGateway.

```java
public interface EmailSenderGateway {
    void enviarEmail(String emailTo, String subject, String body);
}
```
```java
public class GmailSender implements EmailSenderGateway {
  
    private final JavaMailSender mailSender;

    public EmailService(JavaMailSender mailSender) {
        this.mailSender = mailSender;
    }

    @Override
    public void enviarEmail(String emailTo, String subject, String body) throws MailException {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(emailTo);
        message.setSubject(subject);
        message.setText(body);

        mailSender.send(message);
    }
}
```
```java
public class EmailService {
    private final EmailSenderGateway emailSenderGateway;

    public EmailService(EmailSenderGateway emailSenderGateway) {
        this.emailSenderGateway = emailSenderGateway;
    }

    public void enviarEmail(String emailTo, String subject, String body) {
        this.emailSenderGateway.enviarEmail(emailTo, subject, body);
    }
}
```

Com isso, o serviço não depende de ninguém e nem conhece as implementações mais acima, apenas conhece a interface, assim como suas implementações, tornando o código mais flexível.

## Saiba mais
Deixo aqui uns vídeos para caso você queira se aprofundar mais no assunto, recomendo que leia os [livros](https://www.amazon.com.br/Livros-Robert-C-Martin/s?rh=n%3A6740748011%2Cp_27%3ARobert+C.+Martin) do [Robert C Martin](https://en.wikipedia.org/wiki/Robert_C._Martin), o "Uncle Bob", pois ele difundiu muito dessas ideias e é referência na nossa área.

{% embed https://www.youtube.com/watch?v=mkx0CdWiPRA&t=379s&pp=ygUfc29saWQgZGljaW9uYXJpbyBkbyBwcm9ncmFtYWRvcg%3D%3D %}

{% embed https://www.youtube.com/watch?v=6SfrO3D4dHM&pp=ygUfc29saWQgZGljaW9uYXJpbyBkbyBwcm9ncmFtYWRvcg%3D%3D %}

{% embed https://www.youtube.com/watch?v=J2ugvRObGT4&pp=ygUfc29saWQgZGljaW9uYXJpbyBkbyBwcm9ncmFtYWRvcg%3D%3D %}


## Referências

[Alura](https://www.alura.com.br/artigos/solid)
[Trybe](https://blog.betrybe.com/linguagem-de-programacao/solid-cinco-principios-poo/)
[Ugonna Thelma](https://medium.com/backticks-tildes/the-s-o-l-i-d-principles-in-pictures-b34ce2f1e898)