Princípios SOLID Aplicados no Mundo Real: Exemplos Práticos e Boas Práticas

Princípios SOLID Aplicados no Mundo Real: Exemplos Práticos e Boas Práticas

“`html




Princípios SOLID aplicados no mundo real

Arquitetura de software
SOLID na prática

Princípios SOLID aplicados no mundo real

Eu uso SOLID como um checklist de decisões — não como burocracia.
A ideia é transformar código “funcionando” em código evolutivo sem virar uma bola de neve.

S — Single Responsibility Separar motivos de mudança

No mundo real, a falha mais comum não é “classes enormes” apenas por tamanho.
O problema é quando uma mesma classe muda por motivos diferentes.
Exemplo: a mesma classe decide negócio, chama API externa, monta payload e trata logs.

Regra prática que eu sigo: se eu consigo descrever “por que essa classe precisaria mudar”
em duas frases diferentes, provavelmente ela deveria ser separada.

  • Camadas de responsabilidade: domínio decide “o que”, aplicação coordena “como”, infraestrutura executa “com que tecnologia”.
  • Separar formatação: DTOs/mapeamentos não devem estar misturados com regras de cálculo.
  • Testabilidade: quando a classe tem dependências externas, eu isolo o acesso para testar a lógica sem rede, disco ou banco.

Resultado: você reduz o acoplamento e diminui o custo de alteração.
Em vez de “arrumar X e quebrar Y”, você passa a mudar uma coisa por vez com impacto controlado.

O — Open/Closed Estender sem editar o que já funciona

Open/Closed, na prática, é sobre estabilizar pontos do código.
Eu separo um “contrato” do comportamento, para que novas variações entrem via implementação.

Onde isso aparece com força:
gateways de pagamento, estratégias de preço, políticas de elegibilidade, regras de cálculo por tipo de cliente.

  • Interfaces e contratos: “o que fazer” vira uma abstração.
    “como fazer” vira implementações separadas.
  • Orquestração onde precisa existir: o código que coordena chama a interface,
    em vez de ficar cheio de if/else por tipo.
  • Registro de implementações: mesmo quando há um lugar central, eu evito editar lógica do domínio para cada novo caso.
    Novas regras entram como novas classes.

O benefício aparece no seu roadmap: novas exigências deixam de ser “edições em cascata”
e viram “adições previsíveis”.

L — Liskov Substitution Herança que não trai o contrato

Esse princípio costuma ser ignorado quando a equipe usa herança como atalho.
Liskov exige que qualquer substituição por subtipo preserve o comportamento esperado.

Em linguagem simples: se uma classe base diz “funciona assim”, a subclasse deve respeitar as regras,
não pode reduzir garantias nem mudar o significado dos retornos.

  • Evite subclasses que quebram invariantes (ex.: sobrescrevem método para lançar exceções em casos que antes funcionavam).
  • Cuidado com “contratos” base que escondem suposições.
    Se a base permite qualquer entrada e a subclasse não, você quebrou LSP.
  • Se houver diferença real de modelo, prefira composição e estratégias a heranças forçadas.

Quando LSP é respeitado, seus testes ficam mais confiáveis e suas refatorações menos traumáticas.

D — Dependency Inversion Depender de abstrações, não de detalhes

Dependency Inversion é onde o “SOLID” vira entrega.
Na prática, eu tento garantir que regras de negócio não dependam diretamente de APIs,
bibliotecas específicas ou motores de persistência.

O padrão mental que funciona:
o domínio e a aplicação definem contratos; a infraestrutura implementa.

  • Mensagens e repositórios por interface: o serviço usa uma abstração para salvar/ler, sem saber o banco.
  • Clientes externos: gateway/adapter por contrato, para trocar provedor sem reescrever o fluxo.
  • Sem “new” no meio do negócio: criação de dependências fica fora (camada de montagem/boot).

Exemplo prático Pedido com regras extensíveis e dependências invertidas

Considere um cenário comum: calcular desconto e emitir nota.
Eu quero adicionar novas regras de desconto e trocar o provedor de emissão sem tocar no fluxo principal.
A seguir, um exemplo simplificado mostrando como SOLID “encaixa”.

// Contratos (DIP + OCP)
public interface DiscountPolicy {
    boolean supports(Pedido pedido);
    Money apply(Pedido pedido);
}

public interface InvoiceIssuer {
    void issue(Invoice invoice);
}

public interface PedidoRepository {
    Pedido getById(String id);
    void save(Pedido pedido);
}

// Regra de negócio (S: uma responsabilidade; O: extensão por implementação)
public final class PedidoService {

    private final PedidoRepository repo;
    private final java.util.List<DiscountPolicy> policies;
    private final InvoiceIssuer issuer;

    public PedidoService(
        PedidoRepository repo,
        java.util.List<DiscountPolicy> policies,
        InvoiceIssuer issuer
    ) {
        this.repo = repo;
        this.policies = policies;
        this.issuer = issuer;
    }

    public void processar(String pedidoId) {
        Pedido pedido = repo.getById(pedidoId);

        // Open/Closed: novas políticas entram como novas classes
        Money totalDesconto = Money.zero();
        for (DiscountPolicy policy : policies) {
            if (policy.supports(pedido)) {
                totalDesconto = totalDesconto.plus(policy.apply(pedido));
            }
        }

        pedido.aplicarDesconto(totalDesconto);
        repo.save(pedido);

        // Fluxo não sabe “como” emite, só usa o contrato
        Invoice invoice = Invoice.from(pedido);
        issuer.issue(invoice);
    }
}

// Extensões: adiciona regra sem editar PedidoService (OCP)
public final class DescontoClienteFiel implements DiscountPolicy {
    public boolean supports(Pedido pedido) {
        return pedido.cliente().isFiel();
    }
    public Money apply(Pedido pedido) {
        return pedido.valor().multiply(0.10);
    }
}

// Exemplo de implementação de infraestrutura (DIP)
public final class IssuerProviderX implements InvoiceIssuer {
    public void issue(Invoice invoice) {
        // chama API do provedor X e trata detalhes técnicos
    }
}
            
Como os princípios aparecem aqui:

S (políticas e serviço têm foco), O (políticas extensíveis), D (contratos desacoplam).

Nota sobre Liskov: eu não forcei herança. Em vez disso, usei implementações de
DiscountPolicy. Isso reduz o risco de subtipo violar comportamento.

Próximo passo: aplicar em um caso real do seu projeto

Se você gostou dessa visão, continua comigo nos próximos posts: eu trago padrões de organização,
estratégias para reduzir acoplamento e exemplos práticos de refatoração com impacto mínimo.


Dica rápida

Escolha uma classe “difícil” do seu projeto e responda:
por que ela muda? Se a resposta tiver mais de um motivo, você já achou o primeiro ponto para aplicar SOLID.

yurideveloper.com • Escrito para ser aplicado no dia a dia: menos teoria solta, mais decisões com efeito real.



“`