
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↳.NET113 conteúdosNovidades do .NET 9 – o tipo genérico OrderedDictionaryDev (Back & Front) · dez 2024Novidades .NET 10: novas formas de uso do .NET CLIDev (Back & Front) · dez 2025Novidades do .NET 5: implementando um proxy reverso com YARP + ASP.NET 5Dev (Back & Front) · nov 2020Ver tudo em Dev (Back & Front) →.
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







