Duas definições contraditórias do Princípio de Segregação de Interface - qual é a correta?

14

Ao ler artigos no ISP, parece haver duas definições contraditórias do ISP:

De acordo com a primeira definição (consulte 1 , 2 , 3 ), o ISP afirma que as classes que implementam a interface não devem ser forçadas a implementar funcionalidades de que não precisam. Assim, interface gordaIFat

interface IFat
{
     void A();
     void B();
     void C();
     void D();
}

class MyClass: IFat
{ ... }

deve ser dividido em interfaces menores ISmall_1eISmall_2

interface ISmall_1
{
     void A();
     void B();
}

interface ISmall_2
{
     void C();
     void D();
}

class MyClass:ISmall_2
{ ... }

pois desta forma o meu MyClassé capaz de implementar apenas os métodos de que necessita ( D()e C()), sem ser forçado a também fornecer implementações dummy para A(), B()e C():

Mas, de acordo com a segunda definição (ver 1 , 2 , resposta de Nazar Merza ), o ISP afirma que MyClientchamar métodos MyServicenão deve estar ciente de métodos MyServiceque não precisam. Em outras palavras, se MyClientapenas precisar da funcionalidade de C()e D(), em vez de

class MyService 
{
    public void A();
    public void B();
    public void C();
    public void D();
}

/*client code*/      
MyService service = ...;
service.C(); 
service.D();

devemos separar MyService'smétodos em interfaces específicas do cliente :

public interface ISmall_1
{
     void A();
     void B();
}

public interface ISmall_2
{
     void C();
     void D();
}

class MyService:ISmall_1, ISmall_2 
{ ... }

/*client code*/
ISmall_2 service = ...;
service.C(); 
service.D();

Assim, com a definição anterior, o objetivo do ISP é " facilitar a vida das classes implementando a interface IFat ", enquanto que com a última, o objetivo do ISP é " facilitar a vida dos clientes que chamam métodos do MyService ".

Qual das duas definições diferentes de ISP está realmente correta?

@MARJAN VENEMA

1

Então, quando você vai dividir o IFat em uma interface menor, quais métodos acabam em que ISmallinterface deve ser decidido com base na coesão dos membros.

Embora faça sentido colocar métodos coesos na mesma interface, pensei que, com o padrão ISP, as necessidades do cliente têm precedência sobre a "coesão" de uma interface. Em outras palavras, pensei que, com o provedor de serviços de Internet, deveríamos agrupar na mesma interface os métodos necessários para determinados clientes, mesmo que isso signifique deixar de fora dessa interface os métodos que deveriam, por uma questão de coesão, também serem colocados nessa mesma interface?

Assim, se havia muitos clientes que apenas precisavam ligar CutGreens, mas não também GrillMeat, para aderir ao padrão de ISP, devemos colocar apenas CutGreensdentro ICook, mas não também GrillMeat, mesmo que os dois métodos sejam altamente coesos ?!

2)

Penso que a sua confusão deriva da suposição oculta na primeira definição: que as classes de implementação já estão seguindo o princípio da responsabilidade única.

Ao "implementar classes que não seguem o SRP", você está se referindo às classes que implementam IFatou às classes que implementam ISmall_1/ ISmall_2? Presumo que você esteja se referindo a classes que implementam IFat? Se sim, por que você acha que eles ainda não seguem o SRP?

obrigado

EdvRusj
fonte
4
Por que não pode haver várias definições que são ambas servidas pelo mesmo princípio?
21413 Bobson
5
Essas definições não se contradizem.
Mike Partridge
1
É claro que a necessidade do cliente não tem precedência sobre a coesão de uma interface. Você pode usar essa maneira de "regra" de longe e acabar com interfaces de método único em todo o lugar que não fazem absolutamente nenhum sentido. Pare de seguir as regras e comece a pensar nos objetivos para os quais essas regras foram criadas. Com "classes que não seguem o SRP", eu não estava falando sobre nenhuma classe específica no seu exemplo ou que elas já não estavam seguindo o SRP. Leia de novo. A primeira definição leva apenas à divisão de uma interface se a interface não estiver seguindo o ISP e a classe estiver seguindo o SRP.
Marjan Venema
2
A segunda definição não se importa com os implementadores. Ele define interfaces da perspectiva dos chamadores e não faz suposições sobre se os implementadores já existem ou não. Provavelmente, assume que quando você segue o ISP e implementa essas interfaces, é claro que seguiria o SRP ao criá-las.
Marjan Venema
2
Como você sabe de antemão quais clientes existirão e quais métodos eles precisarão? Você não pode. O que você pode saber antes é o quão coesa é a sua interface.
Tulains Córdova

Respostas:

6

Ambos estão corretos

Pelo que li, o objetivo do ISP (Princípio de Segregação de Interface) é manter as interfaces pequenas e focadas: todos os membros da interface devem ter uma coesão muito alta. Ambas as definições têm como objetivo evitar interfaces "jack-of-all-trades-master-of-none".

A segregação de interface e o SRP (princípio de responsabilidade única) têm o mesmo objetivo: garantir componentes de software pequenos e altamente coesos. Eles se complementam. A segregação de interfaces garante que as interfaces sejam pequenas, focadas e altamente coesas. Seguir o princípio da responsabilidade única garante que as aulas sejam pequenas, focadas e altamente coesas.

A primeira definição mencionada se concentra nos implementadores, a segunda nos clientes. Que, ao contrário de @ user61852, considero os usuários / chamadores da interface, não os implementadores.

Penso que a sua confusão deriva da suposição oculta na primeira definição: que as classes de implementação já estão seguindo o princípio da responsabilidade única.

Para mim, a segunda definição, com os clientes como os chamadores da interface, é uma maneira melhor de alcançar o objetivo pretendido.

Segregando

Na sua pergunta, você declara:

pois dessa maneira meu MyClass é capaz de implementar apenas os métodos necessários (D () e C ()), sem ser forçado a também fornecer implementações fictícias para A (), B () e C ():

Mas isso está virando o mundo de cabeça para baixo.

  • Uma classe que implementa uma interface não determina o que precisa na interface que está implementando.
  • As interfaces determinam quais métodos uma classe de implementação deve fornecer.
  • Os chamadores de uma interface são realmente os que determinam qual funcionalidade eles precisam que a interface forneça para eles e, portanto, o que um implementador deve fornecer.

Então, quando você vai se dividir IFatem uma interface menor, quais métodos acabam em qual ISmallinterface deve ser decidida com base na coesão dos membros.

Considere esta interface:

interface IEverythingButTheKitchenSink
{
     void DoDishes();
     void CleanSink();
     void CutGreens();
     void GrillMeat();
}

Quais métodos você adotaria ICooke por quê? Quer colocar CleanSinkjuntamente com GrillMeatsó porque acontecer de você ter uma classe que faz exatamente isso e um par de outras coisas, mas nada como qualquer um dos outros métodos? Ou você o dividiria em mais duas interfaces coesas, como:

interface IClean
{
     void DoDishes();
     void CleanSink();
}

interface ICook
{
     void CutGreens();
     void GrillMeat();
}

Nota de declaração da interface

Uma definição de interface deve, de preferência, estar por conta própria em uma unidade separada, mas se for absolutamente necessário conviver com o chamador ou com o implementador, deve realmente ser com o chamador. Caso contrário, o chamador obtém uma dependência imediata do implementador, o que está anulando completamente o objetivo das interfaces. Consulte também: Declarando interface no mesmo arquivo que a classe base, é uma boa prática? sobre programadores e por que devemos colocar interfaces com classes que as usam e não com aquelas que as implementam? no StackOverflow.

Marjan Venema
fonte
1
Você pode ver a atualização que fiz?
precisa saber é o seguinte
"o chamador obtém uma dependência imediata do implementador " ... somente se você violar o DIP (princípio de inversão de dependência), se as variáveis ​​internas do chamador, parâmetros, valores de retorno, etc. forem do tipo em ICookvez do tipo SomeCookImplementor, conforme determina o DIP, tem que depender SomeCookImplementor.
Tulains Córdova
@ user61852: Se a declaração da interface e o implementador estiverem na mesma unidade, eu imediatamente obtenho uma dependência desse implementador. Não necessariamente em tempo de execução, mas certamente no nível do projeto, simplesmente pelo fato de ele estar lá. O projeto não pode mais ser compilado sem ele ou o que quer que ele use. Além disso, a injeção de dependência não é a mesma que o princípio de inversão de dependência. Você pode estar interessado em DIP na natureza
Marjan Venema
Reutilizei seus exemplos de código nesta pergunta programmers.stackexchange.com/a/271142/61852 , melhorando-o depois que ele já foi aceito. Eu lhe dei o devido crédito pelos exemplos.
Tulains Córdova
Arrefecer @ user61852 :) (e graças para o crédito)
Marjan Venema
14

Você confunde a palavra "cliente" usada nos documentos do Gang of Four com um "cliente" como consumidor de um serviço.

Um "cliente", conforme pretendido pelas definições do Gang of Four, é uma classe que implementa uma interface. Se a classe A implementa a interface B, eles dizem que A é um cliente de B. Caso contrário, a frase "clientes não devem ser forçados a implementar interfaces que não usam" não faria sentido, já que "clientes" (como nos consumidores) não implementar qualquer coisa. A frase só faz sentido quando você vê "cliente" como "implementador".

Se "cliente" significasse uma classe que "consome" (chama) os métodos de outra classe que implementa a grande interface, chamar os dois métodos mais importantes e ignorar o resto seria o suficiente para mantê-lo dissociado do restante da os métodos que você não usa.

O espírito do princípio é evitar que o "cliente" (a classe que implementa a interface) precise implementar métodos fictícios para cumprir com toda a interface quando se importa apenas com um conjunto de métodos relacionados.

Também visa ter a menor quantidade de acoplamento possível, para que as alterações feitas em um só lugar causem menos impacto. Segregando as interfaces, você reduz o acoplamento.

Esses problemas aparecem quando a interface faz muito e tem métodos que devem ser divididos em várias interfaces em vez de apenas uma.

Ambos os exemplos de código estão OK . É apenas que no segundo você assume "cliente" significa "uma classe que consome / chama os serviços / métodos oferecidos por outra classe".

Não encontro contradições nos conceitos explicados nos três links que você deu.

Apenas mantenha claro que "cliente" é implementador , no SOLID talk.

Tulains Córdova
fonte
Mas de acordo com @pdr, enquanto exemplos de código em todos os links aderem ao ISP, a definição do ISP é mais sobre "isolar o cliente (uma classe que chama métodos de outra classe) de saber mais sobre o serviço" do que em " prevenção de clientes (implementadores) serem forçados a implementar interfaces que não usam ".
EdvRusj
1
@EdvRusj Minha resposta é baseada nos documentos do site Object Mentor (empresa Bob Martin), escritos pelo próprio Martin quando ele estava no famoso Gang of Four. Como você sabe, o Gnag of Four era um grupo de engenheiros de software, incluindo Martin, que cunhou o acrônimo SOLID, identificou e documentou os princípios. docs.google.com/a/cleancoder.com/file/d/…
Tulains Córdova
Então você discorda do @pdr e, portanto, considera a primeira definição de ISP (veja meu post original) mais agradável?
EdvRusj
@ EdvRusj Acho que ambos estão certos. Mas o segundo adiciona confusão desnecessária usando a metáfora cliente / servidor. Se eu tiver que escolher um, eu escolheria o grupo dos quatro, que é o primeiro. Mas o que é importante é reduzir o acoplamento e as dependências desnecessárias, afinal o espírito dos princípios do SOLID. Não importa qual deles está certo. O importante é que você deve segregar interfaces de acordo com os comportamentos. Isso é tudo. Mas em caso de dúvida, basta ir à fonte original.
Tulains Córdova
3
Eu discordo muito da sua afirmação de que "cliente" é implementador no SOLID talk. Por um lado, é um absurdo linguístico chamar um provedor (implementador) de cliente do que ele está fornecendo (implementando). Também não vi nenhum artigo sobre o SOLID que tente transmitir isso, mas posso simplesmente ter perdido isso. Mais importante ainda, ele configura o implementador de uma interface como aquele que decide o que deve estar na interface. E isso não faz sentido para mim. Os chamadores / usuários de uma interface definem o que precisam de uma interface e os implementadores (plural) dessa interface são obrigados a fornecê-la.
Marjan Venema
5

O ISP visa isolar o cliente de saber mais sobre o serviço do que ele precisa saber (protegendo-o contra alterações não relacionadas, por exemplo). Sua segunda definição está correta. Na minha leitura, apenas um desses três artigos sugere o contrário ( o primeiro ) e é completamente errado. (Editar: Não, não errado, apenas enganoso.)

A primeira definição está muito mais fortemente ligada ao LSP.

pdr
fonte
3
No ISP, os clientes não devem ser forçados a CONSUMIR componentes de interface que eles não usam. No LSP, os SERVIÇOS não devem ser forçados a implementar o método D porque o código de chamada requer o método A. Eles não são contraditórios, são complementares.
PDR
2
@EdvRusj, o objeto que implementa a InterfaceA que o ClientA chama pode de fato ser exatamente o mesmo objeto que implementa a InterfaceB necessária para o Cliente B. Nos raros casos em que o mesmo cliente precisa ver o mesmo objeto em diferentes Classes, o código não geralmente "toque". Você o verá como um A para um propósito e um B para o outro.
Amy Blankenship
1
@ EdvRusj: Pode ajudar se você repensar sua definição de interface aqui. Nem sempre é uma interface em termos de C # / Java. Você poderia ter um serviço complexo com várias classes simples agrupadas, de forma que o cliente A use a classe de wrapper AX para "fazer interface" com o serviço X. Portanto, quando você altera X de uma maneira que afeta A e AX, você não está forçado a afetar BX e B.
PDR
1
@ EdvRusj: Seria mais preciso dizer que A e B não se importam se ambos estão chamando X ou um está chamando Y e o outro chamando Z. Esse é o ponto fundamental do ISP. Assim, você pode escolher qual implementação deseja e facilmente mudar de idéia mais tarde. O ISP não favorece uma rota ou outra, mas o LSP e o SRP podem.
PDR
1
@EdvRusj Não, o cliente A poderá substituir o Serviço X pelo Serviço y, os quais implementariam a interface AX. X e / ou Y podem implementar outras interfaces, mas quando o cliente as chama como AX, ele não se importa com essas outras interfaces.
Amy Blankenship