C# - Violações dos princípios SOLID - IV


 Hoje vamos continuar mostrando as violações dos principíos SOLID que fazem parte do dia a dia de todo o desenvolvedor.

Continuando o artigo anterior veremos agora a violação do princípio SOLID SRP.

O princípio SOLID SRP (Single Responsibility Principle, ou Princípio da Responsabilidade Única, em português) é um dos cinco princípios SOLID da programação orientada a objetos. Ele estabelece que uma classe deve ter uma única responsabilidade bem definida e ser modificada apenas por uma única razão.

O princípio SRP prega que cada classe deve ter uma única tarefa ou responsabilidade no sistema e que essa responsabilidade deve estar completamente encapsulada dentro da própria classe. Isso significa que uma classe não deve fazer mais do que uma coisa, e não deve ter conhecimento ou depender de outras classes para realizar sua responsabilidade.

Ao seguir o princípio SRP, o código fica mais fácil de entender, manter e evoluir no futuro, pois as classes são mais coesas e independentes umas das outras. Além disso, as mudanças em uma parte do sistema não afetam outras partes, tornando o código mais modular e escalável.

Violação do princípio SRP

Uma violação comum do princípio SRP é a presença de uma classe que tem muitas responsabilidades diferentes, ou seja, que faz muitas coisas diferentes dentro do sistema. Isso pode acontecer quando uma classe é modificada várias vezes para atender a diferentes requisitos ou necessidades do sistema, sem levar em consideração a coesão das responsabilidades.

Por exemplo, imagine uma classe chamada "Funcionario" que é responsável por lidar com todas as informações do funcionário, como registro de horas trabalhadas, folha de pagamento, cálculo de impostos, gerenciamento de benefícios, entre outros. Essa classe estaria violando o princípio SRP, pois tem muitas responsabilidades diferentes.

Para resolver essa violação do SRP, seria necessário separar as responsabilidades da classe "Funcionario" em classes distintas e coesas, como "RegistroDeHoras", "FolhaDePagamento", "CalculoDeImpostos", "GerenciamentoDeBeneficios", entre outras. Dessa forma, cada classe teria uma única responsabilidade bem definida, tornando o código mais fácil de entender, manter e evoluir no futuro.

Veja um exemplo de código que ilustra essa violação usando o C# 11 no VS 2022 com o recurso do Top Level Statement:

public class Funcionario
{
 
public int Id { get; set; }
 
public string Nome { get; set; }
 
public string Email { get; set; }
 
public decimal Salario { get; set; }

 
public void RegistrarHorasTrabalhadas(DateTime data,
                                        int
horasTrabalhadas)
  {
   
// Lógica para registrar horas trabalhadas
  }
 
public void CalcularSalario()
  {
   
// Lógica para calcular o salário
  }
 
public decimal CalcularImpostos()
  {
   
// Lógica para calcular Impostos
  }

  public
void GerenciarBeneficios()
  {
    
// Lógica para gerenciar benefícios
  }
}

Nesse exemplo, a classe "Funcionario" tem as propriedades para armazenar informações do funcionário e quatro métodos, um para registrar as horas trabalhadas, outro para calcular o salário,  outro para calcular os impostos e outro para  gerenciar os benefícios do funcionário. Essa classe tem mais de uma responsabilidade, o que torna mais difícil entender, testar e manter o código no futuro.

Para resolver essa violação do princípio SRP, poderíamos dividir as responsabilidades em outras classes distintas e coesas. Por exemplo, ter uma classe para armazenar informações do funcionário, outra para calcular o salário, outra para calcular impostos e outra para gerenciar os benefícios. Dessa forma, cada classe teria uma única responsabilidade bem definida, o que tornaria o código mais modular e fácil de entender, testar e manter no futuro.

A seguir temos um esboço de código que mostra uma implementação possível para resolver este problema:

public class Funcionario
{
 
public int Id { get; set; }
 
public string Nome { get; set; }
 
public string Email { get; set; }
 
public decimal Salario { get; set; }
}

public class RegistroDeHorasTrabalhadas
{
 
public void RegistrarHorasTrabalhadas(Funcionario funcionario,
                          DateTime data,
int horasTrabalhadas)
  {
    
// Lógica para registrar horas trabalhadas
  }
}

public class CalculadoraDeSalario
{
  
public decimal CalcularSalario(Funcionario funcionario)
   {
     
// Lógica para calcular o salário
   }
}

public class CalculadoraDeImpostos
{
  
public decimal CalcularImpostos(Funcionario funcionario)
   {
     
// Lógica para calcular impostos
   }
}

public class GerenciadorDeBeneficios
{

   public
void GerenciarBeneficios(Funcionario funcionario)
   {
     
// Lógica para gerenciar benefícios
   }
}

Nesse exemplo, a classe "Funcionario" foi dividida em quatro outras classes: "Funcionario", "RegistroDeHorasTrabalhadas", "CalculadoraDeSalario", "CalculadoraDeImpostos" e "GerenciadorDeBeneficios".

Agora, cada classe tem uma única responsabilidade e está aderente ao princípio SRP.

Ao utilizar essa abordagem, torna-se mais fácil entender, testar e manter o código no futuro, além de permitir que cada classe possa ser alterada independentemente das outras.

Padrões que podem violar o princípio

Um padrão que pode violar o princípio SRP é o padrão "Facade". Esse padrão é usado para fornecer uma interface simplificada para um sistema complexo, encapsulando a lógica do sistema em uma única classe ou conjunto de classes. No entanto, se a classe "Facade" se tornar muito grande e tiver várias responsabilidades, ela pode violar o princípio SRP.

Outro padrão que pode violar o princípio SRP é o padrão "Adapter". Este padrão é usado para adaptar uma interface de classe para outra interface compatível. Se a classe de adaptação também tiver outras responsabilidades além de adaptar a interface, ela poderá violar o princípio SRP.

Exemplo de violação do princípio pelo padrão Adapter

Imagine que você está desenvolvendo uma aplicação de e-commerce que precisa integrar com um serviço de pagamento de terceiros chamado "PaymentGateway". O serviço de pagamento possui uma interface que espera um objeto com as informações do pagamento para que possa processá-lo. No entanto, a aplicação de e-commerce já possui uma classe chamada "Order" que contém todas as informações necessárias para realizar um pagamento, mas não está em conformidade com a interface esperada pelo serviço de pagamento.

Para resolver esse problema, você decide implementar um adaptador que converte a classe "Order" na interface esperada pelo serviço de pagamento. O adaptador é implementado na classe "PaymentGatewayAdapter", que contém um método "ProcessPayment" que converte uma instância da classe "Order" em um objeto compatível com o serviço de pagamento e o envia para o serviço.

No entanto, você também decidiu que a classe "PaymentGatewayAdapter" seria responsável por gerenciar o cache de pedidos processados ​​para evitar pedidos duplicados. Portanto, além de adaptar a classe "Order" para a interface do serviço de pagamento, a classe "PaymentGatewayAdapter" também tem a responsabilidade de gerenciar o cache de pedidos.

Este é um exemplo de violação do princípio SRP pelo padrão Adapter, porque a classe "PaymentGatewayAdapter" tem mais de uma responsabilidade - adaptar a classe "Order" para a interface do serviço de pagamento e gerenciar o cache de pedidos - o que pode tornar a classe difícil de manter e modificar.

Exemplo de código que ilustra a violação:

public class Order
{
    public int Id { get; set; }
    public string CustomerName { get; set; }
    public decimal Total { get; set; }
    // outras propriedades do pedido
}

public interface IPaymentGateway
{
    void ProcessPayment(object paymentData);
    // outras operações do serviço de pagamento
}

public class PaymentGatewayAdapter : IPaymentGateway
{
    private IPaymentGateway paymentGateway;
    private List<int> processedOrders;

    public PaymentGatewayAdapter(IPaymentGateway paymentGateway)
    {
        this.paymentGateway = paymentGateway;
        processedOrders = new List<int>();
    }

    public void ProcessPayment(object paymentData)
    {
        var order = paymentData as Order;
        if (order != null)
        {
            if (!processedOrders.Contains(order.Id))
            {
                // adaptação da classe Order para a interface IPaymentGateway
                var paymentInfo = new
                {
                    CustomerName = order.CustomerName,
                    Total = order.Total
                    // outras informações necessárias para o pagamento
                };

                paymentGateway.ProcessPayment(paymentInfo);

                // adiciona o pedido ao cache de pedidos processados
                processedOrders.Add(order.Id);
            }
        }
    }

    // método que viola o princípio SRP
    public void ClearProcessedOrdersCache()
    {
        processedOrders.Clear();
    }
}

Neste exemplo, a classe "PaymentGatewayAdapter" é responsável por adaptar a classe "Order" para a interface "IPaymentGateway" e gerenciar o cache de pedidos processados.

No entanto, a responsabilidade de gerenciar o cache de pedidos é uma responsabilidade separada e deve ser delegada a outra classe para evitar violação do princípio SRP.

E na próxima parte do artigo veremos a violação do princípio DIP.

E estamos conversados ...

"Há um caminho que ao homem parece direito, mas o fim dele são os caminhos da morte."
Provérbios 14:12

Referências:


José Carlos Macoratti