SOLID -
Resolvendo problemas com Design Patterns
![]() |
Hoje vamos recordar os princípios SOLID e também mostrar que podemos usar os Design Patterns para aplicar os princípios. |
Os princípios SOLID e os Design Patterns são conceitos fundamentais na programação orientada a objetos. Entender como os padrões de projeto implementam os princípios SOLID ajuda desenvolvedores a criar aplicações manuteníveis e escaláveis.
Os princípios SOLID definem boas regras de design, enquanto os Design Patterns
oferecem formas testadas de implementá-las.
- Princípios (SOLID)
→ dizem o que fazer
- Padrões (Patterns) → mostram como fazer
Neste guia, vamos aplicar isso com exemplos realistas no contexto de
aplicações .NET.
1- Princípio da Responsabilidade Única - SRP com Strategy Pattern
SRP - Uma classe deve ter apenas um motivo para mudar.
Strategy Pattern - Permite encapsular diferentes algoritmos em
classes separadas, tornando-os intercambiáveis.
Problema real
Uma classe de pagamento fazendo tudo (muitas responsabilidades)
|
public class
ProcessadorPagamento { public void ProcessarCartao(decimal valor) { } public void ProcessarPix(decimal valor) { } public void ProcessarBoleto(decimal valor) { } } |
Isso viola o SRP: Cada método muda por um motivo diferente e cresce sem controle
Solução
Usando o Strategy
public interface IEstrategiaPagamento { void Processar(decimal valor); } public class PagamentoCartao : IEstrategiaPagamento { public void Processar(decimal valor) { // lógica cartão } } public class PagamentoPix : IEstrategiaPagamento { public void Processar(decimal valor) { // lógica PIX } } |
Uso:
public class ServicoPagamento { private readonly IEstrategiaPagamento _estrategia; public ServicoPagamento(IEstrategiaPagamento estrategia) { _estrategia = estrategia; } public void Executar(decimal valor) { _estrategia.Processar(valor); } } |
Por que isso é melhor ?
- Cada classe tem uma
responsabilidade clara
- Facilita testes
- Permite extensão sem bagunçar
código existente
2- OCP - Open/Close Principle com Decorator Pattern
OCP - Software deve estar aberto para extensão e fechado para
modificação.
Decorator Pattern - Permite adicionar
comportamento a um objeto dinamicamente sem alterar sua estrutura.
Problema real
Toda nova regra exige modificar a classe
public class Pedido { public decimal CalcularTotal(bool adicionarFrete, bool aplicarDesconto) { decimal total = 100; if (adicionarFrete) total += 20; if (aplicarDesconto) total -= 10; return total; } } |
Solução com Decorator
public interface IPedido { decimal CalcularTotal(); } public class PedidoBase : IPedido { public decimal CalcularTotal() => 100; } |
Usando o Decorator:
public class FreteDecorator : IPedido { private readonly IPedido _pedido; public FreteDecorator(IPedido pedido) { _pedido = pedido; } public decimal CalcularTotal() { return _pedido.CalcularTotal() + 20; } } |
Outro:
public class DescontoDecorator : IPedido { private readonly IPedido _pedido; public DescontoDecorator(IPedido pedido) { _pedido = pedido; } public decimal CalcularTotal() { return _pedido.CalcularTotal() - 10; } } |
Uso:
| IPedido pedido = new
PedidoBase(); pedido = new FreteDecorator(pedido); pedido = new DescontoDecorator(pedido); |
Benefícios:
- Não altera código existente
- Temos uma
Extensão limpa
- Composição sobre herança
3- LSP - Liskov Substitution Principle (sem forçar padrão)
LSP - Subtipos devem poder substituir seus tipos base sem quebrar o comportamento esperado.
Problema clássico
Isso quebra o contrato
public class Ave { public virtual void Voar() { } } public class Pinguim : Ave { public override void Voar() { throw new Exception("Não voa"); } } |
Solução
| public interface IAve { void Mover(); } public interface IAveQueVoa { void Voar(); } |
SImplementações
public class Pardal : IAve, IAveQueVoa { public void Mover() => Voar(); public void Voar() { } } public class Pinguim : IAve { public void Mover() => Nadar(); private void Nadar() { } } |
Aqui não tem mágica, o LSP é resolvido com modelagem correta, não com padrão específico
4- ISP - Interface Segregation com Adapter Pattern
ISP - Clientes não devem ser forçados a depender de métodos que
não usam.
Adapter - Permite que interfaces incompatíveis
trabalhem juntas.
Problema real
Nem todo cliente precisa dos dois
public interface IRelatorio { void GerarPdf(); void GerarExcel(); } |
Solução: ISP Aplicado
public interface IRelatorioPdf { void GerarPdf(); } public interface IRelatorioExcel { void GerarExcel(); } |
Adapter em cenário real
Sistema Legado:
public class SistemaLegado { public void GerarArquivo(byte[] dados) { } } |
Usando o Adpater
public interface IGeradorRelatorio { void Gerar(string conteudo); } public class AdaptadorLegado : IGeradorRelatorio { private readonly SistemaLegado _legado; public AdaptadorLegado(SistemaLegado legado) { _legado = legado; } public void Gerar(string conteudo) { var bytes = Encoding.UTF8.GetBytes(conteudo); _legado.GerarArquivo(bytes); } } |
O princípio ISP e padrão Adapter não são dependentes, mas se complementam bem em cenários reais
5- DIP - Dependency Inversion + Dependency Injection
DIP - Dependa de abstrações, não de implementações concretas.
DI - Técnica para fornecer dependências externamente.
Problema real
Forte Acoplamento
| public class ServicoNotificacao { private ServicoEmail _email = new ServicoEmail(); } |
Solução
| public
interface
IServicoMensagem { void Enviar(string mensagem); } |
public class ServicoEmail : IServicoMensagem { public void Enviar(string mensagem) { Console.WriteLine(mensagem); } } |
| public class ServicoNotificacao { private readonly IServicoMensagem _servico; public ServicoNotificacao(IServicoMensagem servico) { _servico = servico; } public void Notificar(string mensagem) { _servico.Enviar(mensagem); } } |
Benefícios:
Baixo acoplamento
Alta testabilidade
Fácil
substituição de implementação
Podemos melhorar este código usando Primary Constructor:
| public class
ServicoNotificacao(IServicoMensagem servico) { public void Notificar(string mensagem) { servico.Enviar(mensagem); } } |
O que mudou na prática?
- Removeu o campo _servico
-
Removeu o construtor explícito
- Código ficou mais direto e enxuto
Mas
a essência continua:
A classe depende de abstração (IServicoMensagem)
A dependência continua sendo injetada
O DIP continua sendo respeitado
Aqui cabe destacar o escopo da variável servico :
O
parâmetro servico: Existe como campo implícito mas não
aparece como _servico.
Evite usar Primary
Constructor quando:
- Precisar de validação complexa no construtor
- Precisar inicializar múltiplos campos com lógica
- Desejar clareza
explícita para iniciantes
E estamos conversados...
"Àquele que não conheceu pecado (Jesus), o fez pecado por nós; para que nele fôssemos feitos justiça de Deus. 2 Coríntios 5:21
Referências:
NET - Unit of Work - Padrão Unidade de ...