SOLID – Interface Segregation Principle (ISP)

Estou aqui, leitores!
Conforme esperado, o artigo de hoje aborda a letra “I” do SOLID, que corresponde ao Interface Segregation Principle, ou ISP. Assim com o LSP, este princípio também está relacionado com o conceito de abstração da orientação a objetos. Observaremos, no artigo, que abstrações genéricas podem prejudicar classes que as implementam, forçando implementações desnecessárias.

Introdução

Há quem diga que, dos quatro pilares da orientação a objetos, a Abstração seria o “menos importante”. É um equívoco. A arquitetura de um projeto é elaborada sob os alicerces da Abstração, já que o processo de “converter” as regras de negócio em classes no sistema é essencial para o sucesso na fase de implementação. O resultado deste procedimento é uma arquitetura flexível e fracamente acoplada em virtude de Interfaces e classes bem delimitadas.

O Interface Segregation Principle (ISP) está intimamente ligado à Abstração e orienta a identificação e definição adequada das Interfaces. O objetivo é evitar que a arquitetura seja comprometida à medida que novos módulos, componentes ou classes são adicionados ao projeto. Veja, portanto, que estamos nos referindo à sustentabilidade da arquitetura.

Na teoria, o ISP traz a seguinte declaração:

“Clients should not be forced to depend upon interfaces that they do not use”
(Clientes não devem ser forçados a depender de Interfaces que eles não utilizam)

Os “clientes”, nessa frase, são representados por classes. Dito isso, a declaração acima significa que as classes da arquitetura não devem implementar Interfaces que não fazem parte do seu contexto. Caso isso ocorra, haverá codificações desnecessárias, métodos sem ação e/ou classes com mais de uma responsabilidade. Veremos tudo isso durante este artigo.

Eu diria que a violação do ISP geralmente ocorre quando existem Interfaces muito genéricas na arquitetura ou generalizadas demais. Tome, como exemplo, a declaração da Interface abaixo:

Um “documento”, por si só, é um termo relativamente genérico e pode ter várias interpretações: “É um documento de texto? Um documento fiscal? Um documento pessoal? Pode ser impresso? É exportável par algum formato? Possui assinatura digital? Pode ser editado?”.

O problema de uma Interface com essa característica é o receio de utilizá-la. Por exemplo, uma nova classe chamada TDocumentoViagem deve implementar a Interface IDocumento?

Bom, pelo nome, sim. Pela abstração, talvez não.

Embora um documento de viagem seja um documento, a Interface pode declarar métodos que não fazem parte da sua abstração, como EnviarParaContabilidade. Não faz sentido, não é?

Cenário de exemplo

Codificaremos, a seguir, um pequeno exemplo para demonstrar essa violação. Em seguida, faremos uma refatoração para eliminá-la.

Considere a Interface abaixo, que declara apenas dois métodos para trabalhar com relatórios:

Há duas classes que implementam essa Interface, referente aos relatórios de movimentação de estoque…

… e contas a receber:

Ambas as classes consultam dados nas tabelas (método ConsultarDados) e disponibilizam uma opção para agrupamento por data (método AgruparPorData). Até o momento, tudo certo.

Por solicitação do cliente, devemos iniciar a codificação de um novo relatório de comprovante de venda.

Bom, já que é um relatório, essa nova classe deverá implementar a Interface IRelatorio para disponibilizar os métodos necessários e também para manter um padrão na arquitetura:

Ops… temos um problema. Este relatório não pode ser agrupado por data, pois consiste em apenas uma venda específica, registrada em uma única data. Porém, como é um método da Interface, a classe é obrigada a implementá-lo. Neste caso, portanto, adicionaremos um Exit para indicar que o método não deve ter ação alguma:

Pois bem, leitores, ao escrever este Exit, violamos o Princípio da Segregação da Interface. A classe TComprovantePedidoVenda foi “forçada” a implementar um método que não condiz com o seu contexto, ferindo a declaração do ISP. Embora o método não tenha ação, a sua implementação deve existir. Podemos afirmar, portanto, que houve uma falha na definição da Interface, ou melhor, na abstração.

No final das contas, este método se torna desnecessário e provavelmente confundirá alguns programadores em codificações futuras. Por exemplo, a chamada abaixo ao método AgruparPorData é válida, não gera erros, mas não serve para absolutamente nada:

Este não é o único problema. O mesmo pode acontecer do modo inverso. Imagine que, por conta das regras da classe de comprovante de venda, um novo método foi adicionado à Interface:

Apesar de ser uma alteração simples, o projeto deixa de compilar. As classes referentes aos relatórios de movimentação de estoque e contas a receber começam a receber o erro abaixo, criticando que o método EnviarParaCliente também deve ser implementado:

Porém, estes relatórios não são enviados a clientes, pois são administrativos e de uso interno na empresa.

Solução? Novas instruções Exit:

Agora ficou bem ruim, não? Para complicar, a tendência é surgir cada vez mais “Exits” nas classes conforme novos relatórios de diferentes contextos forem desenvolvidos.

Aplicando o ISP

De uma forma bem simples, pessoal: a solução é corrigir a abstração ao separar os conceitos. No nosso caso, a Interface IRelatorio deverá ser “quebrada” em Interfaces menores e mais restritas. Para isso, os métodos exclusivos do comprovante de venda serão movidos para uma nova Interface:

Com essa alteração, as Interfaces deixam de ser genéricas e passam a ser mais específicas, com um propósito delimitado. Ao codificar novas classes, há uma certeza maior de qual Interface deverá ser utilizada para a implementação.

Além disso, claro, o ISP também evita os efeitos “magnéticos” causados pelas alterações em Interfaces. Com a aplicação do ISP, garantimos que somente as classes que fazem parte do mesmo contexto serão atualizadas.

Agora, uma reflexão: e se existir uma classe que, de alguma forma, seja um relatório e um comprovante ao mesmo tempo?

Bom, embora não faça muito sentido, isso pode ocorrer, mas a solução também é simples. Basta declarar uma implementação múltipla de Interfaces:

 

Fechou, pessoal?

Espero que o Interface Segregation Principle tenha ficado claro para vocês!
Qualquer dúvida, por favor, entrem em contato!

Abraços e até logo!


 

André Celestino