Princípios SOLID em C#: aplicação avançada e casos práticos

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: quando o SOLID deixa de ser teoria

A maioria dos desenvolvedores conhece a sigla SOLID no nível "a classe deve ser responsável por uma coisa". No entanto, em projetos reais em C# (.NET) com Dependency Injection, microsserviços e padrões assíncronos, seguir os princípios ingenuamente leva ao efeito oposto — complexidade excessiva, inchaço de interfaces e violação de encapsulamento.

Neste artigo, vamos abordar o SOLID não como um conjunto de dogmas, mas como uma ferramenta para tomar decisões arquiteturais. Você aprenderá como aplicar corretamente os princípios em cenários avançados: trabalho com contêineres IoC, composição em vez de herança, tratamento de erros através de interfaces e teste de sagas.



1. Single Responsibility: limites de responsabilidade em DDD

O princípio da responsabilidade única (SRP) é frequentemente confundido com "uma classe — uma função". Na verdade, trata-se da razão para mudar (reason to change). No contexto de C# e Domain-Driven Design, isso significa que uma classe deve mudar apenas por um motivo, relacionado a um único ator ou regra de negócio.



Antipadrão avançado: "Entity Divina"

Considere um exemplo típico de violação do SRP em .NET — a classe OrderEntity, que simultaneamente gerencia o estado do pedido, calcula impostos, envia e-mail e salva dados no banco de dados via Entity Framework.



// ❌ Violação do SRPpublic class OrderEntity{    public int Id { get; set; }    public decimal Total { get; set; }    public string Email { get; set; }

public void CalculateTax() { /* lógica de impostos */ } public void SendConfirmation() { /* envio de e-mail */ } public void SaveToDatabase() { /* EF Core SaveChanges */ }}

Problema: uma alteração na regra de negócio sobre impostos (por exemplo, uma nova lei) levará a uma modificação na classe que também é responsável pelo envio de notificações. Isso aumenta o risco de regressão.



Solução: separação de serviços via DI

// ✅ SRP através de serviçospublic class Order{    public int Id { get; set; }    public decimal Total { get; set; }    // Apenas estado, nenhuma lógica}

public class TaxCalculator : ITaxCalculator{ public decimal Calculate(Order order) { /* ... */ }}

public class EmailService : INotificationService{ public Task SendConfirmation(Order order) { /* ... */ }}

public class OrderRepository : IRepository<Order>{ public Task SaveAsync(Order order) { /* EF Core */ }}

Agora cada classe tem exatamente uma razão para mudar. O contêiner DI (Microsoft.Extensions.DependencyInjection) as conecta através dos construtores.



2. Open/Closed: extensão através de estratégia e decorador

O princípio aberto/fechado (OCP) afirma: uma classe deve estar aberta para extensão, mas fechada para modificação. Em .NET, isso é alcançado não apenas através de herança, mas também através de composição com os padrões Strategy e Decorator.



Caso avançado: validação com crescimento de regras

Imagine que você tem um sistema de validação de pedidos. A versão inicial verificava apenas o valor e o endereço. Um mês depois, foi adicionada a regra "não é possível pedir itens com valor acima do limite". Em vez de modificar a classe OrderValidator, usamos composição.



// ❌ Violação do OCP — modificação de classe existentepublic class OrderValidator{    public bool Validate(Order order)    {        if (order.Total < 0) return false;        if (string.IsNullOrEmpty(order.Address)) return false;        // Nova regra — modificamos a classe        if (order.Total > 10000) return false;        return true;    }}


// ✅ OCP através de composição de validadorespublic interface IValidator<T>{    bool Validate(T entity);}

public class TotalValidator : IValidator<Order>{ public bool Validate(Order order) => order.Total > 0;}

pu

Blogs

Book Recommendations