O Princípio da Segregação de Interfaces

Interface Segregation Principle — ISP

Ricardo Dias
Contexto Delimitado
6 min readDec 2, 2019

--

Este princípio declara que é preciso evitar a implementação de “interfaces ou classes inchadas”, preferindo sempre mantê-las da forma mais coesa e objetiva possível. De acordo com ANICHE (1998), interfaces coesas são aquelas que possuem uma única responsabilidade, ou seja, elas devem cumprir o Princípio da Responsabilidade Única. Se forem coesas, essas interfaces possibilitam um maior reúso, tendem a ser mais estáveis e impedem a ocorrência de “gambiarras”.

Lembrando que quando usamos os termo “interface”, nos referimos à qualquer estrutura de linguagem capaz de impor obrigações. Ou seja, tanto Interfaces como Classes Abstratas são conceitualmente consideradas “interfaces” ou “contratos”, como pode ser conferido no artigo anterior.

Outra terminologia importante presente neste artigo diz respeito a “Clientes e Servidores”. Num primeiro momento, o leitor poderá confundir esses termos com conceitos de back-end (software rodando no Servidor) e front-end (software rodando no Cliente/Navegador). Na engenharia de software, esses termos possuem um significado diferente:

  • Servidor: é uma Classe Servidora, abstrata ou não, que fornece funcionalidade a ser utilizada por uma Classe Cliente;
  • Cliente: é aquela classe que utiliza-se de outras classes para poder funcionar, ou seja, depende de Classes Servidoras.

Ficando claro sobre os termos acima explicados, podemos continuar.

Como explica MARTIN (2002), o Princípio da Segregação de Interfaces foi formulado e usado pela primeira vez em uma consultoria realizada para a Xerox, para a melhoria de um sistema para impressoras que executava uma variedade de tarefas. O software para esse sistema tinha um problema grave de design, onde quase todas as tarefas usavam uma única classe abstrata chamada Worker que continha todos os recursos necessários para a execução de inúmeras operações. Assim, uma tarefa de grampear teria conhecimento de todos os métodos da impressora, embora não houvesse necessidade deles. Na medida que o software crescia, ficava cada vez mais difícil evoluí-lo, tornando inviável o processo de desenvolvimento.

Crie a classe Polvo para que ela faça tudo. Mas não me peça para mexer nela!

Martin solucionou o problema criando uma estrutura parecida com o padrão Template Method (GAMMA et all 1995). O princípio usado (que futuramente se tornou conhecido como Princípio da Segregação de Interfaces), consistiu na criação de uma camada de interface entre a classe Worker e suas classes clientes. Em vez de ter uma grande classe Worker contendo a implementação de todas as operações, foram criadas interfaces específicas para cada tarefa. Isso separou o software em contextos menores, facilitando a manutenção e diminuindo o custo de seu desenvolvimento.

O problema das interfaces inchadas

Segundo MARTIN (2011), classes inchadas causam acoplamentos bizarros e prejudiciais entre seus clientes. Quando um cliente obriga uma mudança na classe inchada, todos os outros clientes serão afetados. Esse problema pode ser solucionado dividindo a interface da classe inchada em várias interfaces específicas para cada cliente.

Usando os exemplos dos artigos anteriores, suponhamos que na implementação dos tipos de funcionários (Gerente, Vendedor e Operário) queira-se obrigar a implementação de métodos específicos para cada um deles. Assim sendo:

  • Gerente deve possuir um método para gerenciarProdutos;
  • Vendedor para venderProduto;
  • Operário para operarEstoque.

Mantendo a estrutura do cenário anterior, seriam adicionadas novas assinaturas na interface IFuncionário para tornar obrigatória a implementação dos métodos. Observe no diagrama a seguir:

Interface inchada IFuncionário, ferindo o Princípio da Segregação de Interfaces

Note que agora, as classes Gerente, Vendedor e Operário são obrigadas a implementar os três novos métodos.

Problema:

Usando como exemplo a classe Vendedor, a regra de negócio a ser implementada não irá utilizar os métodos gerenciarProdutos e operarEstoque, pois não fazem parte da responsabilidade do Vendedor.

Solução para este cenário:

A alternativa para resolver esse problema é implementar os três métodos (mesmo que não sejam usados) e adicionar uma exceção nos “métodos indesejados”. Observe no exemplo de código escrito em PHP:

class Vendedor implements IFuncionário
{
public function calcularSalário(): float
{
// implementa a regra de negócio normalmente
// ...
}
public function gerenciarProdutos(): bool
{
throw new BadMethodCallException(
'Este método não está disponível');

}
public function venderProduto(): bool
{
// implementa a regra de negócio normalmente
// ...
}
public function operarEstoque(): bool
{
throw new BadMethodCallException(
'Este método não está disponível');

}
}

Na tratativa acima, os métodos indesejados foram inutilizados, impossibilitando que um programador desavisado invoque-os erroneamente. Parece uma “boa solução”, pois ao invocar o método errado, a exceção será disparada e o programa será encerrado, revelando a falha na implementação. No entanto, isso é uma “gambiarra”!

“Os clientes não devem ser obrigados a depender de métodos que não utilizam.” (MARTIN 2011)

Como dito no início deste artigo, o termo “clientes” se refere às classes utilizadoras das interfaces, que se forem inchadas causarão acoplamentos bizarros. “Num dado momento, quando algum cliente necessitar de uma mudança na classe inchada, todos os outros clientes serão afetados” (MARTIN 2011).

Segundo MARTIN (2019), “é prejudicial depender de módulos que contenham mais elementos do que você precisa”, justamente porque essas implementações incham as classes com implementações desnecessárias e aumentam o acoplamento entre elas, podendo causar “problemas inesperados” (MARTIN 2019).

Questione-se: eu sozinho realmente preciso de tudo isso?

Usando o Princípio da Segregação de Interfaces

Para entender como funciona o princípio, iremos corrigir o caso anterior, injetando coesão e organização na implementação com base no Princípio da Segregação de Interfaces.

“Muitas interfaces específicas do cliente são melhores que uma interface de propósito geral” (MARTIN 2000)

Para MARTIN (2011), os clientes devem depender apenas dos métodos que chamam. Isso pode ser alcançado dividindo-se a interface da classe inchada em várias interfaces específicas do cliente.

No cenário abordado, será necessário criar mais três interfaces, uma para cada tipo de funcionário, totalizando em quatro interfaces segregadas:

  • IFuncionário: contendo a assinatura para calcularSalário(), comum a todos os funcionários;
  • IGerente: contendo a assinatura do método gerenciarProdutos();
  • IVendedor: contendo a assinatura do método venderProduto();
  • IOperário: contendo a assinatura do método operarEstoque().

Dessa forma será possível, a cada tipo de funcionário, implementar suas próprias obrigações, sem adaptações ou “gambiarras”, como pode ser observado no diagrama abaixo:

Implementação coesa, que cumpre o Principio da Segregação de Interfaces

Para ficar mais claro ainda, observe no exemplo de código escrito em PHP:

class Vendedor implements IFuncionário, IVendedor
{
public function calcularSalário(): float
{
// implementa a regra de negócio normalmente
}
public function venderProduto(): bool
{
// implementa a regra de negócio normalmente
}
}

Agora, a classe Vendedor será obrigada a implementar apenas os dois métodos pelos quais ela é responsável, ou seja, calcularSalário e venderProduto.

Se cada classe faz o que é de sua responsabilidade, o código será mais fácil de entender e manter!

Conclusão

Na prática, a ideia é ter interfaces pequenas e especializadas para cada parte do sistema, dando flexibilidade às implementações e reduzindo o acoplamento.

Assim como todos os princípios, deve-se tomar cuidado para não exagerar. O espectro de uma classe com centenas de interfaces diferentes, algumas segregadas por cliente e outras segregadas por versão, seria realmente assustador. (MARTIN 2000)

Leia também o próximo artigo sobre o assunto:

Leia todos os artigos desta série:

Referências para Aprofundamento

ANICHE, Maurício. Orientação a Objetos e SOLID para ninjas. São Paulo, SP, Brasil. Casa do Código, 1998.

GAMMA, Erich; HELM, Richard; JONSON, Ralph; VLISSIDES, John. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995.

MARTIN, Robert C. Design Principles and Design Patterns. 2000 <https://fi.ort.edu.uy/innovaportal/file/2032/1/design_principles.pdf>. Acesso em 03/08/2019.

MARTIN, Robert C. Agile Software Development: Principles, Patterns and Practices 1st edition. Pearson, 2002.

MARTIN, Robert C. Princípios, Padrões e Práticas Ágeis em C#. Bookman, Porto Alegre, RS, 2011.

MARTIN, Robert C. Arquitetura Limpa: O guia do artesão para estrutura e design de software. Alta Books Editora, Rio de Janeiro, RJ, 2019.

RAVEENDRAN, Prasad. C# Corner. 2019 <https://www.c-sharpcorner.com/article/learn-about-solid-principles>. Acesso em 17/11/2019.

--

--

Ricardo Dias
Contexto Delimitado

Apaixonado por padrões, programação clara, elegante e principalmente manutenível. Trabalha como desenvolvedor deste 2000, incrementando a cada ano este loop…