Princípios SOLID

Contexto do SOLID

Antigamente os sistemas eram desenvolvidos com um único objetivo: fazer o software funcionar. Os padrões utilizados eram monolíticos e rígidos, com forte acoplamento e com tudo interligado. Atualmente sistemas conhecidos como “Código espaguete” ou “Grande Bola de Lama”. Como tudo estava interligado, corrigir um erro em uma ponta do sistema geralmente causava erros em outras partes do sistema.

Crise do Software

Na década de 70 e 80 o desenvolvimento de software enfrentava uma crise. O hardware estava ficando incrivelmente potente e barato, mas o software estava se tornando tão complexo que as equipes não conseguiam mantê-los de forma eficiente.

Introdução ao SOLID

O SOLID veio pra transformar o desenvolvimento de software em uma tarefa artesanal em uma engenharia, focada em criar peças que se encaixam sem estarem “soldadas” umas nas outras. Ele é um conjunto de cinco princípios de design de software para a programação orientada a objetos (POO).

Em resumo, podemos dizer que os princípios funcionam como uma base/guia para os desenvolvedores arquitetarem soluções fáceis de manter, testar e evoluír ao longo do tempo.

Ao invés de desenvolvermos sistemas como “um grande bloco de cimento”, o SOLID nos ensina a construir softwares como peças “LEGO”, que se encaixam de forma inteligente.

1. Single Responsability Principle

Esse é o primeiro pilar do SOLID. A definição clássica é “Uma classse deve ter um, e apenas um motivo pra mudar.”.

Na prática, isso quer dizer que uma classe deve ser responsável por uma funcionalidade/parte específica do software. Em sistemas “blocos de cimentos”, é comum encontrarmos as chamadas “God Objects” (Objeto Deus), uma classe gigantesca que sabem e fazem tudo. O que contradiz o princípio da responsabilidade única.

Analogia

Imagine um funcionário de um restaurante que é, ao mesmo tempo, o cozinheiro, o garçom e o faxineiro . Se ele precisar mudar a forma como limpa o chão, o serviço de cozinha e o atendimento podem ser afetados ou interrompidos. No SRP, dividimos essas tarefas entre especialistas para que um não atrapalhe a evolução do outro.

Exemplo

Vamos imaginar um cenário em que o princípio não foi aplicado e após isso vamos transformá-lo em um código modular. Imagine um sistema que gerencia pedidos de e-commerce.

Exemplo Problema

Na classe Pedido abaixo, temos três motivos diferentes para serem alterados. Se a regra de calculo, banco e e-mail mudar. Cada método representa uma responsabilidade diferente para a classe Pedido

class Pedido:
    def calcular_total(self):
        # Lógica de negócio (Preço, impostos, descontos)
        pass

    def salvar_no_banco(self):
        # Lógica de infraestrutura (Conexão SQL, tabelas)
        pass

    def enviar_email_confirmacao(self):
        # Lógica de comunicação (Servidor SMTP, template do e-mail)
        pass

Exemplo Solução

Para aplicar o SRP, separamos as tarefas em “especialistas”. Cada classe agora tem apenas uma razão para existir e mudar.

  1. Pedido vai conter apenas a lógica de cálculo calcular_total
  2. salvar_no_banco vai ser uma nova classe chamada PedidoRepository, que vai implementar o Padrão Repository
  3. enviar_email_confirmacao vai ser um serviço separado apenas por enviar comunicações

Desse modo, se você decidir trocar o e-mail por uma mensagem no WhatsApp, você altera apenas o serviço respectivo. A regra de cálculo do Pedido então permanece segura e intocada, como uma peça LEGO independente

Exemplo aplicado a contexto: Clean Architecture, Mediator, CQRS e DDD

Veja como o SRP se manifesta em um CommandHandler usando Mediator:

// Este Handler tem UMA única razão para mudar: 
// Se o fluxo de "Criação de Pedido" for alterado.
public class CriarPedidoHandler : IRequestHandler<CriarPedidoCommand, Guid>
{
    private readonly IPedidoRepository _repository; // Interface (DIP)
    private readonly IMediator _mediator;

    public async Task<Guid> Handle(CriarPedidoCommand request, CancellationToken ct)
    {
        // 1. Regra de Negócio (DDD: Entidade decide se pode ser criada)
        var pedido = new Pedido(request.ClienteId, request.Itens);

        // 2. Persistência (Clean Arch: O Handler não sabe se é SQL ou NoSQL)
        await _repository.SalvarAsync(pedido);

        // 3. Comunicação (Mediator: Dispara um evento para quem interessar)
        // Isso separa a criação do pedido do envio de e-mail (SRP puro!)
        await _mediator.Publish(new PedidoCriadoEvent(pedido.Id));

        return pedido.Id;
    }

Leave a Reply