SOLID na Prática! (Parte 4: Interface Segregation)

Thiago Barradas
thiagobarradas
Published in
4 min readMar 1, 2021
Vamos falar de SOLID, além do conceito! Vamos para a prática, agora com o princípio Interface Segregation!

Este artigo é uma continuação de SOLID na Prática! (Parte 3: Liskov Substitution).

Interface Segregation Principle

“Muitas interfaces específicas, são melhores do que uma para todos propósitos. Uma classe não deve ser obrigada a implementar interfaces ou métodos que não serão utilizados.”

Quando pensamos em abstração, precisamos ter muito cuidado para que o nosso código reflita o problema e o propósito de sua existência. Ou seja, a modelagem de sua aplicação, por mais que o contexto seja similar com outras diversas aplicações existentes, pode exigir uma modelagem diferente. A arquitetura da solução e suas partes podem ter estruturas, propriedades e definições diferentes para contextos diferentes. Quem está acostumado a trabalhar com o desenvolvimento orientado ao domínio — DDD — deveria entender bem o que estou dizendo. Se você não conhece bem o termo, dá uma olhadinha sobre Bounded Context e Context Map.

Mas, o que isso tem haver com a segregação de interfaces?

Se você parar para pensar friamente, em nível um pouco mais baixo, uma interface nada mais é do que um contrato que define comportamentos e informações para resolver um ponto específico, um contexto (menor do que o contexto citado acima) com o foco em mapear recursos necessários para algo sem uma implementação em si.

Veja um exemplo de uma interface:

SOLID [I] — Exemplo de uma interface qualquer.

Ao analisar a interface sem saber o seu contexto, é impossível dizer se suas propriedades e métodos são suficientes para resolver o problema. Se faz sentido segmentar em mais interfaces ou se temos elementos desnecessários. Vamos pensar que é uma interface para definir um humano em um jogo. Os métodos Move(...) e Speak(...) fazem um pouco de sentido, mas será que somente os humanos do jogo podem se mover e falar?

Se meu jogo possui uma área possível de movimentação, outros elementos como animais, representados pela interface IAnimal poderiamos segmentar de uma maneira um pouco diferente.

SOLID [I] — Interfaces com herança.

No exemplo acima, foi criado uma interface IMovable para qualquer elemento que se mova no jogo. As interfaces IHuman e IAnimal estão herdando o comportamento de IMovable, o que significa que obrigtoriamente todos humanos e animais do jogo devem se movimentar. Uma classe qualquer que implementa a interface IHuman, precisa também implementar o método Move(...). Se você possuir a definição que alguns humanos não irão se movimentar, você não deve herdar a interface, pois isso é apenas uma forma de segmentar as responsabilidades também em interfaces.

Um método que receba um parâmetro do tipo IMovable, poderá receber tanto instâncias que implementam IHuman, quanto IAnimal.

O método Speak(...) foi mantido como uma caracteristica exclusiva dos humanos. Os animais do jogo emitem sons aleatórios e essa foi definida como uma caracteristica específica para eles. Vamos supor que alguns animais, além de emitir sons aleatórios, podem falar, como humanos (afinal, é um jogo!):

SOLID [I] — Segmentando interfaces.

Nesse novo exemplo, todas as interfaces foram segmentadas permitindo que eu tenha, por exemplo, humanos que não se movem e nem falam, humanos que apenas se movem mas não falam, humanos que falam mas não se movem e humanos que falam e se movem. O exemplo também é aplicável para os animais (IAnimal). Esses comportamentos dependem das interfaces que minha classe implementará, do contexto que ela está envolvida. Obviamente, o restante do sistema deve estar com classes e métodos definidos para receber estes como tipos genéricos ou como parâmetro em seus métodos, para um funcionamento adequado.

Para finalizar os exemplos, decidimos que queremos ter um personagem, humano, que seja um terrorista. Seria uma grande violação incluir na interface IMovable o método Explode(...), afinal, nem todos humanos “explodem” hehe.

SOLID [I] — Adicionando novas interfaces.

Adicionamos a interface IBomb e uma classe pode implementar também esse comportamento, seja um IHuman ou um IAnimalnão que você precise usar esse comportamento, mas com as interfaces segregadas fica muito mais fácil reaproveitar pequenos comportamentos.

Introduzimos no sistema também um novo conceito, o ITerrorist que se tornou praticamente um alias para implementar um humano que explode (IHuman + IBomb).

Dessa maneira, quando eu crio uma nova classe eu vou implementar somente as interfaces que fazem sentido para ela e garantir que não estarei violando o princípio da segregação de interfaces.

Se você está implementando uma interface e alguma coisa não está sendo usada, um método não faz nada, lança uma exceção por não ter implementação, uma propriedade sem referencias ou algo implementado apenas para cumprir o contrato, isso pode ser um sinal que você está violando o princípio da segregação de interfaces e você precisa urgentemente revisar como tudo foi desenhado e projetado.

No próximo artigo, falaremos sobre Dependency Inversion Principle (Princípio da inversão de dependência) a última letra desse acrônimo tão importante no desenvolvimento de software. Para bons códigos, reaproveitaveis, desacoplados e evolutivos!

--

--

Thiago Barradas
thiagobarradas

Microsoft MVP, Elastic Contributor. Entusiasta de novas tecnologias e arquiteto de software na Stone. Cultuador do hábito de formar cabeças pensantes.