O Princípio de Segregação de Interface diz:
Nenhum cliente deve ser forçado a depender dos métodos que não usa. O ISP divide as interfaces muito grandes em menores e mais específicas, para que os clientes precisem apenas conhecer os métodos que lhes interessam.
Existem algumas perguntas não respondidas aqui. Um é:
Quão pequeno?
Você diz:
Atualmente, eu lido com isso dividindo o espaço de nome do módulo, dependendo dos requisitos de seus clientes.
Eu chamo isso de digitação manual de pato . Você cria interfaces que expõem apenas o que um cliente precisa. O princípio de segregação de interface não é simplesmente digitação manual do pato.
Mas o ISP também não é simplesmente um pedido de interfaces de função "coerentes" que podem ser reutilizadas. Nenhum design de interface de função "coerente" pode se proteger perfeitamente contra a adição de um novo cliente com suas próprias necessidades de função.
O ISP é uma maneira de isolar os clientes do impacto das alterações no serviço. O objetivo era tornar a construção mais rápida à medida que você faz alterações. Claro que tem outros benefícios, como não quebrar clientes, mas esse era o ponto principal. Se eu estiver alterando a count()
assinatura da função de serviços , é bom que os clientes que não usam count()
não precisem ser editados e recompilados.
É por isso que me importo com o Princípio de Segregação de Interface. Não é algo que considero importante como fé. Resolve um problema real.
Portanto, a maneira como deve ser aplicada deve resolver um problema para você. Não existe uma maneira mecânica de aplicar ISP que não pode ser derrotada com o exemplo certo de uma mudança necessária. Você deve observar como o sistema está mudando e fazer escolhas que permitirão que as coisas se acalmem. Vamos explorar as opções.
Primeiro, pergunte a si mesmo: está dificultando as alterações na interface de serviço agora? Caso contrário, saia e brinque até se acalmar. Este não é um exercício intelectual. Por favor, verifique se a cura não é pior que a doença.
Se muitos clientes usam o mesmo subconjunto de funções, isso significa interfaces reutilizáveis "coerentes". O subconjunto provavelmente se concentra em torno de uma idéia que podemos considerar como a função que o serviço está fornecendo ao cliente. É bom quando isso funciona. Isso nem sempre funciona.
Se muitos clientes usam diferentes subconjuntos de funções, é possível que o cliente esteja realmente usando o serviço através de várias funções. Tudo bem, mas dificulta a visualização dos papéis. Encontre-os e tente separá-los. Isso pode nos colocar de volta no caso 1. O cliente simplesmente usa o serviço através de mais de uma interface. Por favor, não comece a transmitir o serviço. Se alguma coisa significaria passar o serviço para o cliente mais de uma vez. Isso funciona, mas me faz questionar se o serviço não é uma grande bola de lama que precisa ser quebrada.
Se muitos clientes usam subconjuntos diferentes, mas você não vê funções, mesmo permitindo que os clientes usem mais de um, então você não tem nada melhor do que digitar duck para projetar suas interfaces. Essa maneira de projetar as interfaces garante que o cliente não seja exposto a uma única função que não esteja usando, mas quase garante que a adição de um novo cliente sempre envolverá a adição de uma nova interface que, embora a implementação do serviço não precise saber sobre isso a interface que agrega as interfaces de função. Simplesmente trocamos uma dor por outra.
Se muitos clientes usam subconjuntos diferentes, se sobrepõem, espera-se que novos clientes sejam adicionados, que precisarão de subconjuntos imprevisíveis e você não estiver disposto a interromper o serviço, considere uma solução mais funcional. Como as duas primeiras opções não funcionaram e você está realmente em um lugar ruim, onde nada está seguindo um padrão e mais mudanças estão chegando, considere fornecer a cada função sua própria interface. Terminar aqui não significa que o ISP falhou. Se alguma coisa falhou, foi o paradigma orientado a objetos. As interfaces de método único seguem o ISP ao extremo. É bastante digitado no teclado, mas você pode achar que isso repentinamente torna as interfaces reutilizáveis. Mais uma vez, verifique se não há
Acontece que eles podem ficar muito pequenos.
Fiz essa pergunta como um desafio para aplicar o ISP nos casos mais extremos. Mas tenha em mente que é melhor evitar extremos. Em um design bem pensado que aplica outros princípios do SOLID, esses problemas geralmente não ocorrem ou importam, quase o mesmo.
Outra pergunta sem resposta é:
Quem possui essas interfaces?
Repetidas vezes, vejo interfaces projetadas com o que chamo de mentalidade de "biblioteca". Todos nós somos culpados pela codificação macaco-ver-macaco-do, onde você está apenas fazendo algo porque é assim que você vê isso. Somos culpados da mesma coisa com interfaces.
Quando olho para uma interface projetada para uma aula em uma biblioteca, eu pensava: ah, esses caras são profissionais. Esse deve ser o caminho certo para fazer uma interface. O que eu estava deixando de entender é que o limite de uma biblioteca tem suas próprias necessidades e problemas. Por um lado, uma biblioteca é completamente ignorante do design de seus clientes. Nem todo limite é o mesmo. E às vezes até o mesmo limite tem maneiras diferentes de atravessá-lo.
Aqui estão duas maneiras simples de analisar o design da interface:
Interface de propriedade do serviço. Algumas pessoas projetam todas as interfaces para expor tudo o que um serviço pode fazer. Você pode até encontrar opções de refatoração nos IDE que escreverão uma interface para você usando qualquer classe que for alimentada.
Interface de propriedade do cliente. O ISP parece argumentar que isso está certo e o serviço de propriedade está errado. Você deve dividir todas as interfaces com as necessidades dos clientes. Como o cliente possui a interface, ele deve defini-la.
Então quem está certo?
Considere plugins:
Quem possui as interfaces aqui? Os clientes? Os serviços?
Acontece que ambos.
As cores aqui são camadas. A camada vermelha (direita) não deve saber nada sobre a camada verde (esquerda). A camada verde pode ser alterada ou substituída sem tocar na camada vermelha. Dessa forma, qualquer camada verde pode ser conectada à camada vermelha.
Eu gosto de saber o que deveria saber sobre o que e o que não deveria saber. Para mim, "o que sabe sobre o quê?", É a questão arquitetônica mais importante.
Vamos esclarecer um pouco o vocabulário:
[Client] --> [Interface] <|-- [Service]
----- Flow ----- of ----- control ---->
Um cliente é algo que usa.
Um serviço é algo que é usado.
Interactor
passa a ser ambos.
O ISP diz que interrompe interfaces para clientes. Tudo bem, vamos aplicar isso aqui:
Presenter
(um serviço) não deve ditar a Output Port <I>
interface. A interface deve ser reduzida ao que Interactor
(aqui atuando como cliente) precisa. Isso significa que a interface SABE sobre oe Interactor
, para seguir o ISP, deve mudar com ele. E isso é bom.
Interactor
(aqui atuando como um serviço) não deve ditar a Input Port <I>
interface. A interface deve ser restrita ao que Controller
(um cliente) precisa. Isso significa que a interface SABE sobre oe Controller
, para seguir o ISP, deve mudar com ele. E isso não está bem.
O segundo não é bom porque a camada vermelha não deve saber sobre a camada verde. Então, o ISP está errado? Bem, mais ou menos. Nenhum princípio é absoluto. Este é um caso em que as bobagens que gostam da interface para mostrar tudo o que o serviço pode fazer estão corretas.
Pelo menos, eles estão certos se Interactor
não fizerem nada além do que esse caso de uso precisa. Se o Interactor
fizer para outros casos de uso, não há motivo Input Port <I>
para saber sobre eles. Não sei por Interactor
que não posso focar apenas em um Caso de Uso; portanto, esse não é um problema, mas acontece tudo.
Mas a input port <I>
interface simplesmente não pode se escravizar no Controller
cliente e fazer com que esse seja um verdadeiro plug-in. Este é um limite de 'biblioteca'. Uma loja de programação completamente diferente poderia escrever a camada verde anos após a publicação da camada vermelha.
Se você estiver cruzando um limite de 'biblioteca' e sentir a necessidade de aplicar o ISP, mesmo que não seja o proprietário da interface do outro lado, precisará encontrar uma maneira de restringir a interface sem alterá-la.
Uma maneira de conseguir isso é um adaptador. Coloque entre clientes como Controler
e a Input Port <I>
interface. O adaptador aceita Interactor
como Input Port <I>
e delega seu trabalho. No entanto, ele expõe apenas o que os clientes Controller
precisam através de uma interface de função ou interfaces pertencentes à camada verde. O adaptador não segue o ISP, mas permite que classes mais complexas Controller
gostem do ISP. Isso é útil se houver menos adaptadores do que clientes como Controller
esses e quando você estiver na situação incomum em que está ultrapassando um limite de biblioteca e, apesar de publicada, a biblioteca não para de mudar. Olhando para você Firefox. Agora essas alterações quebram apenas seus adaptadores.
Então o que isso quer dizer? Sinceramente, você não forneceu informações suficientes para eu lhe dizer o que deve fazer. Não sei se não seguir o ISP está causando um problema. Não sei se segui-lo não acabaria causando mais problemas.
Sei que você está procurando um princípio orientador simples. ISP tenta ser isso. Mas deixa muito por dizer. Eu acredito nisso. Sim, por favor, não force os clientes a depender de métodos que eles não usam, sem uma boa razão!
Se você tiver um bom motivo, como projetar algo para aceitar plug-ins, esteja ciente dos problemas que não seguem as causas do ISP (é difícil mudar sem quebrar os clientes) e as maneiras de mitigá-los (mantenha Interactor
ou pelo menos se Input Port <I>
concentre em um estável caso de uso).