Se você já trabalhou em projetos que pareciam fáceis no começo, mas com o tempo viraram um pesadelo de manutenção, provavelmente sofreu com código rígido, acoplado e difícil de escalar.
Os princípios SOLID surgiram exatamente para evitar esse problema. Eles ajudam a escrever um código mais organizado, flexível e fácil de manter. Vamos ver, de forma objetiva e com exemplos práticos, como aplicá-los no dia a dia!
📌 S - Single Responsibility Principle (SRP)
"Uma classe deve ter apenas uma razão para mudar."
Imagine uma classe que faz tudo: processa pedidos, gera nota fiscal e envia e-mails.
public class PedidoService
{
public void ProcessarPedido() { /* lógica */ }
public void GerarNotaFiscal() { /* lógica */ }
public void EnviarEmail() { /* lógica */ }
}
❌ O problema? Sempre que precisar mudar a nota fiscal, pode acabar impactando o envio de e-mails.
✅ Solução: Separar responsabilidades:
public class PedidoProcessor { /* Processa o pedido */ }
public class NotaFiscalService { /* Gera nota fiscal */ }
public class EmailService { /* Envia e-mails */ }
Agora, cada classe tem apenas uma responsabilidade, facilitando a manutenção.
📌 O - Open/Closed Principle (OCP)
"O código deve estar aberto para extensão, mas fechado para modificação."
Imagine um sistema que calcula descontos para diferentes clientes:
public class Pedido
{
public decimal CalcularDesconto(string tipoCliente)
{
if (tipoCliente == "Premium") return 0.2m;
if (tipoCliente == "Regular") return 0.1m;
return 0m;
}
}
❌ O problema? Se surgir um novo tipo de cliente, precisamos modificar essa classe, quebrando o OCP.
✅ Solução: Criar uma estrutura extensível:
public interface IDesconto { decimal Aplicar(); }
public class DescontoPremium : IDesconto { public decimal Aplicar() => 0.2m; }
public class DescontoRegular : IDesconto { public decimal Aplicar() => 0.1m; }
public class Pedido
{
private readonly IDesconto _desconto;
public Pedido(IDesconto desconto) { _desconto = desconto; }
public decimal CalcularDesconto() => _desconto.Aplicar();
}
Agora, podemos adicionar novos descontos sem mexer no código existente.
📌 L - Liskov Substitution Principle (LSP)
"Objetos de uma subclasse devem poder substituir objetos da superclasse sem quebrar o código."
Imagine que temos uma classe Ave
que define um comportamento comum para todas as aves:
public class Ave
{
public virtual void Voar()
{
Console.WriteLine("A ave está voando!");
}
}
Agora, queremos adicionar um pinguim ao nosso código, mas ele não pode voar! Se criarmos uma subclasse Pinguim
e sobrescrevermos Voar()
, teremos um problema:
public class Pinguim : Ave
{
public override void Voar()
{
throw new NotImplementedException("Pinguins não voam!");
}
}
❌ O problema? Se um código espera receber uma Ave
, mas recebe um Pinguim
, pode acabar chamando Voar()
e quebrando a aplicação.
✅ Solução: Precisamos reorganizar a hierarquia e evitar forçar um comportamento que nem todas as subclasses podem ter.
public abstract class Ave { }
public interface IVoavel
{
void Voar();
}
public class Andorinha : Ave, IVoavel
{
public void Voar() => Console.WriteLine("A andorinha está voando!");
}
public class Pinguim : Ave
{
public void Nadar() => Console.WriteLine("O pinguim está nadando!");
}
Agora, garantimos que somente as aves que realmente voam implementem IVoavel
, e evitamos problemas de substituição.
📌 I - Interface Segregation Principle (ISP)
"Uma interface não deve forçar uma classe a implementar métodos que ela não usa."
Suponha que temos uma interface IAcaoFuncionario
que obriga todas as classes a implementarem métodos desnecessários:
public interface IAcaoFuncionario
{
void Trabalhar();
void Gerenciar();
}
public class Desenvolvedor : IAcaoFuncionario
{
public void Trabalhar() { /* lógica */ }
public void Gerenciar() { throw new NotImplementedException(); }
❌ O problema? Um desenvolvedor não gerencia, mas é obrigado a implementar o método.
✅ Solução: Criar interfaces menores e específicas:
public interface ITrabalho { void Trabalhar(); }
public interface IGerencia { void Gerenciar(); }
public class Desenvolvedor : ITrabalho
{
public void Trabalhar() { /* lógica */ }
}
Agora, cada classe implementa apenas o que precisa.
📌 D - Dependency Inversion Principle (DIP)
"Módulos de alto nível não devem depender de módulos de baixo nível, mas sim de abstrações."
Suponha que um serviço de pedidos dependa diretamente de um serviço de pagamento:
public class PedidoService
{
private readonly PagamentoService _pagamentoService = new PagamentoService();
public void ProcessarPedido() { _pagamentoService.RealizarPagamento(); }
}
❌ O problema? Se PagamentoService
mudar, PedidoService pode quebrar.
✅ Solução: Usar injeção de dependência e abstrações:
public interface IPagamentoService { void RealizarPagamento(); }
public class PedidoService
{
private readonly IPagamentoService _pagamentoService;
public PedidoService(IPagamentoService pagamentoService)
{
_pagamentoService = pagamentoService;
}
public void ProcessarPedido() { _pagamentoService.RealizarPagamento(); }
}
Agora, podemos trocar a implementação de pagamento sem alterar PedidoService
.
🎯 Conclusão
Os princípios SOLID não são apenas teoria — eles ajudam a criar código mais flexível e de fácil manutenção. Mas lembre-se: nem tudo precisa ser superabstraído. O segredo é aplicar SOLID com equilíbrio, garantindo um código limpo sem exageros.
E você, já enfrentou código que ignorava SOLID? Comenta aqui sua experiência! 🚀
Top comments (0)