Aplicando SOLID com C# – ISP (Interface Segregation Principle) | .NET Coders
Chegamos a quarta parte da série sobre os padrões SOLID apresentados aqui no NET Coders. Hoje o assunto abordado será o ISP (Interface Segregation Principle) ou Princípio da Segregação de Interface.
Uma pitada de Programação Orientada a Objetos
Antes de entender o que é o ISP, vamos falar um pouco sobre POO (Programação Orientada a Objeto) e Interfaces. Um dos grandes paradigmas da POO diz que é muito melhor programar para uma abstração do que para uma implementação. O uso das interfaces torna o código menos acoplado, extensível e assim gerando menos problemas em manutenções ou adições de novas funcionalidades. Embora com "popularização" da POO, muitos desenvolvedores de diversos níveis de experiência, principalmente a galera que vem do Delphi e/ou do VB6, ainda tem problemas para usar interfaces e programar em alto nível de abstração. Acabam deixando um dos maiores recursos da POO de lado ou fazendo uso inadequado.
Voltando ao ISP…
Este princípio é bem simples de entender e sua aplicação é justamente em oposição a interfaces genéricas demais, onde dependendo do cenário, pode acabar ocorrendo o problema de uma classe ter que implementar métodos que não usará.
O ISP possui um mantra que diz:
Outra frase bastante usada para definir o ISP é:
Clientes não devem ser forçados a depender de métodos que não usam
Podemos traduzir "Clientes" para uma classe, que pode ser um serviço ou um repositório, por exemplo.
É bem comum este princípio ser aplicado para resolver problemas de violação do LSP (veja aqui o artigo sobre o LSP), em alguns casos, um acaba complementando o outro.
O ISP ajuda quando uma determinada interface se torna "gorda" ou genérica demais com muitas responsabilidades e aplicando esse padrão, podemos subdividir essa interface em partes menores com responsabilidades mais específicas (assim fazendo valer seu mantra).
Vou apresentar um cenário bem comum do nosso dia-a-dia. E como nos artigos anteriores, antes vou mostrar a violação e depois a solução.
Teremos um repositório genérico com operações de escrita e leitura, porém vamos precisar segregar essas atividades, pois existem objetos que não podem ter métodos de escrita e sim apenas métodos de leitura, aplicando o ISP, vamos resolver esse problema.
Na Listagem 01 eis o nosso repositório genérico com as operações de CRUD (Create, Read, Update e Delete).
void Gravar(TEntity obj);
IEnumerable<TEntity> ObterTodos();
TEntity ObterPorId(int id);
Listagem 01 – IBaseRepository
Até aqui tudo normal, uma interface usada para aplicar o Repository Pattern para operações de escrita e leitura.
Em nosso contexto temos a classe Cliente que terá a sua própria implementação de IBaseRepository apresentado na Listagem 02.
public class ClienteRepository: IBaseRepository<Cliente>
public void Gravar(Cliente obj)
//ação de gravar
public void Atualizar(Cliente obj)
//ação de atualizar
public IEnumerable<Cliente> ObterTodos()
//ação de listar todos
public Cliente ObterPorId(int id)
//ação de listar por id
Listagem 02 – Repositório de Cliente implementado.
Violando o ISP
Em todas as classes que é necessário ter as operações de CRUD eu posso implementar essa interface sem problema algum, mas e se precisar implementar o repositório em uma entidade que tenha somente operações de leitura? Por exemplo uma entidade Cidade.
Na Listagem 03 veremos que se implementarmos o IBaseRepository para Cidade vamos violar o ISP já que os métodos Gravar e Atualizar simplesmente não fazem parte do contexto da entidade Cidade.
public class CidadeRepository :IBaseRepository<Cidade>
public void Gravar(Cidade obj)
throw new Exception("Não existe operação de gravação para Cidade");
public void Atualizar(Cidade obj)
throw new Exception("Não existe operação de atualização para Cidade");
public IEnumerable<Cidade> ObterTodos()
//ação para obter todas as cidades
public Cidade ObterPorId(int id)
//ação para obter uma cidade por id
Listagem 03 – Violando o ISP.
Como temos a necessidade de implementar repositórios em entidades que podem ou não possuir operações de escrita, fazendo uso do ISP vamos dividir uma única interface genérica em duas interfaces com responsabilidades específicas.
A interface IBaseRepository dará lugar a duas novas interfaces:
IBaseLeituraRepository – para leitura dos dados.
IBaseEscritaRepository – Para escrita dos dados.
public interface IBaseEscritaRepository<TEntity> where TEntity : class
public interface IBaseLeituraRepository<TEntity> where TEntity : class
Listagem 04 – ISP aplicado a interface IBaseRepository
A classe ClienteRepository, agora em vez de ter uma única interface com operações de leitura e escrita, terá uma interface para ler os dados e outra interface para escrever os dados.
public class ClienteRepository: IBaseEscritaRepository<Cliente>, IBaseLeituraRepository<Cliente>
//ação listar todos
Listagem 05 – Mudança da implementação de ClienteRepository.
A classe CidadeRepository que violou o ISP anteriormente, agora com essa nova interface de leitura não precisará implementar métodos que não usa.
public class CidadeRepository:IBaseLeituraRepository<Cidade>
Listagem 06 – Violação do ISP resolvida em CidadeRepository.
Bom, no último artigo da série sobre SOLID, o assunto abordado será o DIP (Dependency Inversion Principle), que por sua vez é aplicado em conjunto com o ISP. Ao longo de cada artigo, estamos vendo que cada ideia defendida pelo SOLID nada mais é que um complemento da outra e juntas formam uma maneira coesa de desenvolver Orientado a Objeto. Até lá!