Dev (Back & Front)ARTIGO

SOLID – Resolvendo problemas com Design Patterns

SOLID – Resolvendo problemas com Design Patterns
Imagem: José Carlos Macoratti

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)

aspnet
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

aspnet
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:

aspnet
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

aspnet
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

aspnet
public interface IPedido
{
    decimal CalcularTotal();
}
public class PedidoBase : IPedido
{
    public decimal CalcularTotal() => 100;
}

Usando o Decorator:

aspnet
public class FreteDecorator : IPedido
{
    private readonly IPedido _pedido;
    public FreteDecorator(IPedido pedido)
    {
        _pedido = pedido;
    }
    public decimal CalcularTotal()
    {
        return _pedido.CalcularTotal() + 20;
    }
}

Outro:

aspnet
public class DescontoDecorator : IPedido
{
    private readonly IPedido _pedido;
    public DescontoDecorator(IPedido pedido)
    {
        _pedido = pedido;
    }
    public decimal CalcularTotal()
    {
        return _pedido.CalcularTotal() - 10;
    }
}

Uso:

aspnet
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

aspnet
public class Ave
{
    public virtual void Voar() { }
}
public class Pinguim : Ave
{
    public override void Voar()
    {
        throw new Exception("Não voa");
    }
}

 

Solução

aspnet
public interface IAve
{
    void Mover();
}

public interface IAveQueVoa
{
   void Voar();
}

 

SImplementações

aspnet
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

aspnet
public interface IRelatorio
{
    void GerarPdf();
    void GerarExcel();
}

Solução: ISP Aplicado

aspnet
public interface IRelatorioPdf
{
    void GerarPdf();
}
public interface IRelatorioExcel
{
    void GerarExcel();
}

Adapter em cenário real

Sistema Legado:

aspnet
public class SistemaLegado
{
    public void GerarArquivo(byte[] dados) { }
}

Usando o Adpater

aspnet
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

aspnet
public class ServicoNotificacao
{
     private ServicoEmail _email = new ServicoEmail();
}

Solução

aspnet

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:

aspnet
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

 

é referência em Visual Basic no Brasil e autor dos livros "Aprenda Rápido: ASP" e "ASP, ADO e Banco de Dados na Internet". Mantenedor do site macoratti.net.

Ver perfil