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_1
eISmall_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 MyClient
chamar métodos MyService
não deve estar ciente de métodos MyService
que não precisam. Em outras palavras, se MyClient
apenas 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's
mé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 CutGreens
dentro 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 IFat
ou à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
fonte
Respostas:
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:
Mas isso está virando o mundo de cabeça para baixo.
Então, quando você vai se dividir
IFat
em uma interface menor, quais métodos acabam em qualISmall
interface deve ser decidida com base na coesão dos membros.Considere esta interface:
Quais métodos você adotaria
ICook
e por quê? Quer colocarCleanSink
juntamente comGrillMeat
só 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: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.
fonte
ICook
vez do tipoSomeCookImplementor
, conforme determina o DIP, tem que dependerSomeCookImplementor
.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.
fonte
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.
fonte