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


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

Vamos iniciar com a violação do princípio ISP - Interface Segregation Principle - que afirma que uma classe deve ter apenas uma única razão para mudar e que as interfaces devem ser segregadas de modo que os clientes só dependam das funcionalidades que precisam.

Violações do princípio ISP

Podemos elencar alguns dos principais exemplos onde a violação deste princípio ocorre:

1- Interface com muitos métodos: Uma interface que contém muitos métodos pode ser um sinal de que ela não está seguindo o princípio ISP. Isso ocorre porque os clientes que implementam a interface podem precisar implementar métodos que não são relevantes para eles, resultando em uma maior complexidade e acoplamento.

Um exemplo pode ser uma interface IUser que contenha muitos métodos, incluindo métodos para autenticação, criação de usuários e gerenciamento de permissões. Isso pode ser resolvido dividindo a interface em interfaces menores e mais específicas, como IAuthentication, IUserCreation e IPermissionManagement.

2- Classes que implementam interfaces irrelevantes: Às vezes, as classes podem precisar implementar interfaces que têm métodos irrelevantes para elas. Isso pode ser um sinal de que a interface não está seguindo o princípio ISP e está tentando fazer muito.

Um exemplo pode ser uma classe que implementa a interface IUser que também contém métodos para gerenciamento de permissões, mas essa classe não precisa gerenciar permissões. Isso pode ser resolvido dividindo a interface IUser em interfaces menores e mais específicas e fazendo com que as classes implementem apenas as interfaces relevantes para elas.

3- Dependências desnecessárias: Se uma classe depende de uma interface que contém métodos irrelevantes para ela, ela pode estar violando o princípio ISP. Isso ocorre porque a classe está sendo forçada a depender de algo que não precisa, o que aumenta o acoplamento e a complexidade.

Por exemplo, se uma classe precisa apenas de um método para autenticar usuários, mas depende de uma interface IUser que contém muitos outros métodos, isso pode ser resolvido dividindo a interface IUser em interfaces menores e mais específicas e fazendo com que a classe dependa apenas da interface relevante para ela.

4- Sobrecarga de métodos: Às vezes, uma interface pode exigir que as classes implementem muitos métodos, o que pode levar à sobrecarga de métodos. Isso pode ser um sinal de que a interface não está seguindo o princípio ISP, pois está tentando fazer muito.

Um exemplo pode ser uma interface que exige que as classes implementem métodos para adicionar, atualizar e excluir dados, além de outros métodos. Isso pode ser resolvido dividindo a interface em interfaces menores e mais específicas, como IAddData, IUpdateData e IDeleteData.

Um exemplo clássico é o repositório genérico pois fornece um conjunto amplo de métodos que um cliente pode não precisar. Por exemplo, um repositório genérico pode oferecer métodos para criar, ler, atualizar e excluir dados, mas um cliente pode precisar apenas de um subconjunto desses métodos para realizar sua tarefa específica. Isso pode levar a um acoplamento desnecessário entre o cliente e o repositório, resultando em um design menos flexível e mais difícil de manter.

A seguir temos um exemplo da definição de um contrato de um repositório genérico:

public interface IRepositorio<T> where T : class
 {
        IQueryable<T> GetTodos();
        IQueryable<T> Get(Expression<Func<T, bool>> predicate);
        T Procurar(params object[] key);
        T Primeiro(Expression<Func<T, bool>> predicate);
        void Adicionar(T entity);
        void Atualizar(T entity);
        void Deletar(Func<T, bool> predicate);
        void Commit();
        void Dispose();

 }

Se tivermos por exemplo um repositório UsuarioRepository que gerencia usuários mas que tem como regra de negócio não permitir que o usuário seja excluído, ao usar um repositório genérico nesta implementação estaremos ferindo o princípio SOLID ISP, pois ao usar o repositório genérico o repositório de usuários vai herdar a implementação da exclusão do usuário do repositório genérico mesmo não precisando disso.

 public interface IUsuarioRepositorio : IRepositorio<Usuario>
 {
     //código
  }

Uma forma de resolver esse problema é usar composição ao invés de herança ou dividir a interface do Repositório genérico em interfaces mais específicas que atendam às necessidades de cada classe individualmente.

Padrões que podem violar o ISP

Um padrão comum que pode violar o princípio ISP é o padrão "Abstract Factory". Este padrão é usado para fornecer uma interface para criar famílias de objetos relacionados sem especificar suas classes concretas. No entanto, se uma classe que usa a interface da fábrica precisar implementar todos os métodos da interface, mesmo aqueles que não usa, ela pode violar o princípio ISP.

Outro padrão que pode violar o princípio ISP é o padrão "Bridge". Este padrão é usado para separar uma abstração de sua implementação, permitindo que elas variem independentemente. No entanto, se a abstração tiver que depender de métodos que não usa na implementação, ela pode violar o princípio ISP.

Violação pelo padrão Abstract Factory

A seguir vou mostrar um exemplo que ilustra como o padrão "Abstract Factory" pode violar o princípio ISP.

Vamos considerar um cenário em que temos uma fábrica abstrata que cria diferentes tipos de conexões de banco de dados. A fábrica abstrata tem uma interface comum com diferentes métodos para criar diferentes tipos de conexões de banco de dados.

public interface ConnectionFactory
{
   Connection createSqlConnection();
   Connection createOracleConnection();
   Connection createMySqlConnection();
  
//... outros métodos para criar diferentes tipos de conexões
}

Agora, vamos supor que temos duas classes, CustomerDAO e OrderDAO, que precisam se conectar a diferentes bancos de dados. Para isso, elas usam a fábrica abstrata para criar as conexões de banco de dados.

public class CustomerDAO
{
    private ConnectionFactory connectionFactory;
    public CustomerDAO(ConnectionFactory connectionFactory)
    {
        this.connectionFactory = connectionFactory;
    }
    public void saveCustomer(Customer customer)
    {
        Connection connection = connectionFactory.createSqlConnection();
        // salva o cliente no banco de dados
    }
}
public class OrderDAO
{
    private ConnectionFactory connectionFactory;
    public OrderDAO(ConnectionFactory connectionFactory)
    {
        this.connectionFactory = connectionFactory;
    }
    public void saveOrder(Order order)
    {
        Connection connection = connectionFactory.createOracleConnection();
        // salva o pedido no banco de dados
    }
}

Note que, para usar a fábrica abstrata, as classes CustomerDAO e OrderDAO precisam implementar todos os métodos da interface ConnectionFactory, mesmo que não precisem de todas as conexões de banco de dados.

Por exemplo, OrderDAO não precisa da conexão MySQL, mas ainda assim precisa implementar o método createMySqlConnection(). Isso viola o princípio ISP, pois as classes estão sendo forçadas a depender de métodos que não precisam.

Para evitar essa violação, podemos refatorar a interface ConnectionFactory em várias interfaces menores, cada uma com um conjunto específico de métodos para criar conexões de banco de dados. Dessa forma, as classes CustomerDAO e OrderDAO podem implementar apenas as interfaces que precisam.

public interface SqlConnectionFactory
{
   Connection createSqlConnection();
}


public
interface OracleConnectionFactory
{
   Connection createOracleConnection();
}


public
interface MySqlConnectionFactory
{
   Connection createMySqlConnection();
}

 

Com essa abordagem, as classes CustomerDAO e OrderDAO implementam apenas as interfaces que precisam, evitando a violação do princípio ISP.

No próximo artigo continuamos abordando as violações do SOLID.

E estamos conversados ...

"Porque toda a criatura de Deus é boa, e não há nada que rejeitar, sendo recebido com ações de graças. Porque pela palavra de Deus e pela oração é santificada."
1 Timóteo 4:4-5

Referências:


José Carlos Macoratti