Princípio de Segregação de Interface

Olá pessoal, vou fazer alguns posts sobre S.O.L.I.D. O primeiro que gostaria de abordar com vocês é:

  S
  O
  L
  ISP - Interface Segregation Principle
  D

A definição basicamente é:

“Clients should not be forced to depend upon interfaces that they do not use.”

Para mais detalhes sobre a definição, segue o link do artigo The Interface Segregation Principle que é um resumo de um capitulo do livro “Agile Principles, Patterns, and Practices in C#” de Martin C. Robert e Martin Micah.

Gostaria de mostrar um exemplo real onde o principio é quebrado. O exemplo está em C#.

O meu problema era que eu tinha uma interface onde todas as classes que precisavam fazer alguma persistência tinham que implementar, nesta interface eu tinha definido três métodos: Save, Delete e GetBy, porém, nem todas as classes precisavam implementar todos estes métodos.

IRepository.cs
1
2
3
4
5
6
7
public interface IRepository<TEnt>
where TEnt : Entity {

TEnt Get(int id);
TEnt Save(TEnt entity);
void Delete(TEnt entity);
}

O próximo código mostra uma interface para definir as operações que uma ordem poderá realizar na base de dados, já que a mesma estende da interface IRepository.

IOrderRepository.cs
1
public interface IOrderRepository : IRepository<Order> { }

O código abaixo é a classe que irá implementar os métodos definidos na interface IOrderRepository que por sua vez irá implementar os métodos definidos na interface IRepository já que a interface IOrderRepository estende da interface IRepository.

OrderRepository.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class OrderRepository : IOrderRepository {

public Order Get(int id) {
//Implementacao da busca da ordem no banco.
}

public Order Save(Order entity) {
//Implementacao da insercao/atualizacao da ordem no banco.
}

public void Delete(Order entity) {
//Este metodo eu nao vou utilizar porem ele esta aqui porque
//a interface IRepository obriga que eu o implemente.
throw new NotImplementedException();
}
}

Percebem que o método Delete tem um throw new NotImplementedException(), porque eu não vou usar este método para nada, ele só está ai porque está definido na minha interface IRepository, e por este motivo eu sou obrigado a implementá-lo, o problema é que quando eu for utilizar a classe OrderRepository não tem como eu saber se o método delete tem implementação ou não e quando eu chamar o mesmo irá gerar uma exceção. Este é um exemplo que quebra o principio ISP.

Abaixo uma forma de resolver este problema. Notem que eu quebrei a interface IRepository em outras três interfaces:

  • IDeleteRepository: Que irá conter a definição do método Delete.
  • IGetRepository: Que irá conter a definição do método GetBy.
  • ISaveRepository: Que irá conter a definição do método Save.

A interface IRepository agora estende das três interfaces definidas acima, caso tenha alguma interface que precise definir as três operações, pode estender da interface IRepository.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface IDeleteRepository<in TEnt>
where TEnt : Entity {

void Delete(TEnt entity);
}

public interface IGetRepository<out TEnt>
where TEnt : Entity {

TEnt Get(int id);
}

public interface ISaveRepository<TEnt>
where TEnt : Entity {

TEnt Save(TEnt entity);
}

public interface IRepository<TEnt>
: ISaveRepository<TEnt>,
IDeleteRepository<TEnt>,
IGetRepository<TEnt>
where TEnt : Entity
{ }

Notem agora que a interface IOrderRepository não estende mais a interface IRepository e sim as interfaces: IGetRepository e ISaveRepository.

IOrderRepository.cs
1
2
3
public interface IOrderRepository
: IGetRepository<Order>, ISaveRepository<Order>
{ }

Agora a nossa classe OrderRepository não contém mais o método Delete.

OrderRepository.cs
1
2
3
4
5
6
7
8
9
10
public class OrderRepository : IOrderRepository {

public Order Get(int id) {
//Implementacao da busca da ordem no banco.
}

public Order Save(Order entity) {
//Implementacao da insercao/atualizacao da ordem no banco.
}
}

Vocês podem notar que para resolver o problema do throw new NotImplementedException() eu tive que criar interfaces mais especificas, e a nossa classe final contém agora apenas os métodos que realmente precisa.

O código abaixo não é importante para o nosso exemplo, será mostrando apenas para completar os códigos acima.

Está é a classe base das minhas entidades. Como podem notar a interface IRepository pede a definição de um tipo que herde está classe.
Note que ela contém uma propriedade chamada Id do tipo int, caso você tenha a necessidade de ter em cada entidade uma propriedade Id de um tipo diferente você precisa apenas deixar genérico o tipo desta propriedade, como é apenas um exemplo não adicionei está complexidade a minha classe.

Entity.cs
1
2
3
4
public class Entity {

public int Id { get; protected set; }
}

A classe abaixo é a minha classe de Order, note que ela herda da classe Entity que foi explicada acima.

Order.cs
1
public class Order : Entity { }

Obrigado pela visita e espero que tenha gostado, qualquer dúvida é só entrar em contato.