DEV Community

Cover image for Guia de S.O.L.I.D. com Java
Crystian Lefundes
Crystian Lefundes

Posted on

Guia de S.O.L.I.D. com Java

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 para que compreenda melhor este post.

O que é SOLID?

SOLID é um acrônimo idealizado por Micheal Feathers que unificou os princípios da orientação a objetos apresentados inicialmente por Robert C Martin, mais conhecido como "Uncle Bob" em seu artigo chamado "The principles of OoD" de 1995, a sigla significa:

Imagem com a sigla SOLID e seus significados em inglês

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

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:

public String geraVerificaESalvaSenha() {
  // Implementação do código...
}
Enter fullscreen mode Exit fullscreen mode

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.

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...
}

Enter fullscreen mode Exit fullscreen mode

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:

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...
  }

}
Enter fullscreen mode Exit fullscreen mode

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:

public class Notificação {
  public void notificar() {
     // Implementação do código... 
  }
}
Enter fullscreen mode Exit fullscreen mode
public class LavaLoucas {
  public void limpar() {
     // Implementação do código... 
  }
}
Enter fullscreen mode Exit fullscreen mode
public class Copo {
  public void encher() {
    // Implementação do código...
  }

  public void esvaziar() {
    // Implementação do código...
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

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

public class CLT {
  public int salario() {
    // Implementação do código...
  }
}
Enter fullscreen mode Exit fullscreen mode
public class Estagio {
  public int bolsaAuxilio() {
    // Implementação do código...
  }
}
Enter fullscreen mode Exit fullscreen mode
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();
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

public interface Funcionario {
  int remuneracao();
}
Enter fullscreen mode Exit fullscreen mode
public class CLT implements Funcionario {

  @Override
  public int remuneracao() {
    // Implementação do código...
  }
}
Enter fullscreen mode Exit fullscreen mode
public class Estagio implements Funcionario {

  @Override
  public int remuneracao() {
    // Implementação do código...
  }
}
Enter fullscreen mode Exit fullscreen mode
public class FolhaDePagamento {
  private int saldo;

  public int calcular(Funcionario funcionario) {
    this.saldo = funcionario.remuneracao();
  }
}
Enter fullscreen mode Exit fullscreen mode

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

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.

public class MediaPlayer {
  public void reprodruzir() {
    System.out.println("Reproduzindo mídia...");
  }
}
Enter fullscreen mode Exit fullscreen mode
public class Mp3Player {
  public void reprodruzir() {
    System.out.println("Reproduzindo som...");
  }
}
Enter fullscreen mode Exit fullscreen mode
public class Mp4Player {
  public void reprodruzir() {
    System.out.println("Reproduzindo vídeo...");
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

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.

public interface Trabalho {
  void comecarATrabalhar();

  void almocar();

  void carregarBateria();

  void terminarTrabalho();
}
Enter fullscreen mode Exit fullscreen mode
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...
  }
}
Enter fullscreen mode Exit fullscreen mode
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...
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

public class Trabalho {
   public void comecarATrabalhar() {
      // Implementação de código...
   }

   public void terminarTrabalho() {
      // Implementação de código...
   }
}
Enter fullscreen mode Exit fullscreen mode
public class Robo extends Trabalho {
   public void carregarBateria() {
     // Implementação de código...
   }
}
Enter fullscreen mode Exit fullscreen mode
public class Humano extends Trabalho {
    public void almocar() {
      // Implementação de código...
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

public interface EmailSenderGateway {
    void enviarEmail(String emailTo, String subject, String body);
}
Enter fullscreen mode Exit fullscreen mode
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);
    }
}
Enter fullscreen mode Exit fullscreen mode
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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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 do Robert C Martin, o "Uncle Bob", pois ele difundiu muito dessas ideias e é referência na nossa área.

Referências

Alura
Trybe
Ugonna Thelma

Top comments (0)