Al leer artículos sobre ISP, parece haber dos definiciones contradictorias de ISP:
Según la primera definición (ver 1 , 2 , 3 ), el ISP establece que las clases que implementan la interfaz no deberían verse obligadas a implementar funcionalidades que no necesitan. Por lo tanto, interfaz gruesaIFat
interface IFat
{
void A();
void B();
void C();
void D();
}
class MyClass: IFat
{ ... }
debe dividirse en interfaces más pequeñas ISmall_1
yISmall_2
interface ISmall_1
{
void A();
void B();
}
interface ISmall_2
{
void C();
void D();
}
class MyClass:ISmall_2
{ ... }
ya que de esta manera my MyClass
puede implementar solo los métodos que necesita ( D()
y C()
), sin verse obligado a proporcionar implementaciones ficticias para A()
, B()
y C()
:
Pero de acuerdo con la segunda definición (ver 1 , 2 , respuesta de Nazar Merza ), el ISP afirma que MyClient
llamar a los métodos MyService
no debe ser consciente de los métodos MyService
que no necesita. En otras palabras, si MyClient
solo necesita la funcionalidad de C()
y D()
, en lugar de
class MyService
{
public void A();
public void B();
public void C();
public void D();
}
/*client code*/
MyService service = ...;
service.C();
service.D();
debemos segregar los MyService's
métodos en interfaces específicas del 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();
Por lo tanto, con la primera definición, el objetivo del ISP es " facilitar la vida de las clases que implementan la interfaz IFat ", mientras que con la segunda, el objetivo del ISP es " facilitar la vida de los clientes que llaman métodos de MyService ".
¿Cuál de las dos definiciones diferentes de ISP es realmente correcta?
@MARJAN VENEMA
1)
Entonces, cuando va a dividir IFat en una interfaz más pequeña, qué métodos terminan en qué ISmallinterface debe decidirse en función de la cohesión de los miembros.
Si bien tiene sentido colocar métodos cohesivos dentro de la misma interfaz, pensé que con el patrón ISP las necesidades del cliente tienen prioridad sobre la "cohesión" de una interfaz. En otras palabras, pensé que con ISP deberíamos agrupar dentro de la misma interfaz aquellos métodos necesarios para clientes particulares, incluso si eso significa dejar fuera de esa interfaz aquellos métodos que, por el bien de la cohesión, también deberían ponerse dentro de esa misma interfaz.
Por lo tanto, si había muchos clientes que solo necesitarían llamar CutGreens
, pero no también GrillMeat
, entonces, para adherirnos al patrón de ISP, ¿solo deberíamos ponerlos CutGreens
dentro ICook
, pero no también GrillMeat
, a pesar de que los dos métodos son altamente cohesivos?
2)
Creo que su confusión se deriva de una suposición oculta en la primera definición: que las clases implementadoras ya están siguiendo el principio de responsabilidad única.
Al "implementar clases que no siguen SRP", ¿se refiere a las clases que implementan IFat
oa las clases que implementan ISmall_1
/ ISmall_2
? ¿Asumo que te refieres a clases que implementan IFat
? Si es así, ¿por qué supone que no siguen SRP?
Gracias
fuente
Respuestas:
Ambos son correctos
Según lo leo, el propósito del ISP (Principio de segregación de interfaz) es mantener las interfaces pequeñas y enfocadas: todos los miembros de la interfaz deben tener una cohesión muy alta. Ambas definiciones están destinadas a evitar las interfaces "jack-of-all-trades-master-of-none".
La segregación de interfaz y el SRP (Principio de responsabilidad única) tienen el mismo objetivo: garantizar componentes de software pequeños y altamente coherentes. Se complementan entre sí. La segregación de interfaz garantiza que las interfaces sean pequeñas, enfocadas y altamente cohesivas. Seguir el principio de responsabilidad única asegura que las clases sean pequeñas, enfocadas y altamente cohesivas.
La primera definición que menciona se centra en los implementadores, la segunda en los clientes. Lo cual, a diferencia de @ user61852, considero que son los usuarios / llamantes de la interfaz, no los implementadores.
Creo que su confusión se deriva de una suposición oculta en la primera definición: que las clases implementadoras ya están siguiendo el principio de responsabilidad única.
Para mí, la segunda definición, con los clientes como llamadores de la interfaz, es una mejor manera de llegar al objetivo deseado.
Segregando
En su pregunta usted declara:
Pero eso está poniendo el mundo patas arriba.
Entonces, cuando se va a dividir
IFat
en una interfaz más pequeña, qué métodos terminan en quéISmall
interfaz se debe decidir en función de la cohesión de los miembros.Considere esta interfaz:
¿Qué métodos pondrías
ICook
y por qué? ¿Le pusoCleanSink
junto conGrillMeat
sólo porque le sucede que tiene una clase que hace precisamente eso y un par de otras cosas, pero nada como cualquiera de los otros métodos? ¿O lo dividirías en dos interfaces cohesivas más, como:Nota de declaración de interfaz
Una definición de interfaz debería estar sola en una unidad separada, pero si necesita vivir con la persona que llama o con el implementador, realmente debería estar con la persona que llama. De lo contrario, la persona que llama obtiene una dependencia inmediata del implementador, lo que anula el propósito de las interfaces por completo. Consulte también: Declarar interfaz en el mismo archivo que la clase base, ¿es una buena práctica? en Programadores y ¿Por qué deberíamos colocar interfaces con clases que las usan en lugar de aquellas que las implementan? en StackOverflow.
fuente
ICook
lugar de tipoSomeCookImplementor
, como lo exige DIP, entonces no 't tiene que dependerSomeCookImplementor
.Confunde la palabra "cliente" como se usa en los documentos de la Banda de los Cuatro con un "cliente" como consumidor de un servicio.
Un "cliente", según lo previsto por las definiciones de la Banda de los Cuatro, es una clase que implementa una interfaz. Si la clase A implementa la interfaz B, entonces dicen que A es un cliente de B. De lo contrario, la frase "los clientes no deberían verse obligados a implementar interfaces que no usan" no tendría sentido ya que los "clientes" (como en los consumidores) no No implemente nada. La frase solo tiene sentido cuando ve "cliente" como "implementador".
Si "cliente" se refiere a una clase que "consume" (llama) los métodos de otra clase que implementa la interfaz grande, entonces llamar a los dos métodos que le interesan e ignorar el resto, sería suficiente para mantenerlo desconectado del resto de los métodos que no usas
El espíritu del principio es evitar que el "cliente" (la clase que implementa la interfaz) tenga que implementar métodos ficticios para cumplir con toda la interfaz cuando solo le importa un conjunto de métodos relacionados.
También tiene como objetivo tener la menor cantidad de acoplamiento posible para que los cambios realizados en un solo lugar causen el menor impacto. Al segregar las interfaces, reduce el acoplamiento.
Ese problema aparece cuando la interfaz hace demasiado y tiene métodos que deberían dividirse en varias interfaces en lugar de solo una.
Ambos ejemplos de código están bien . Es solo que en el segundo asume que "cliente" significa "una clase que consume / llama a los servicios / métodos ofrecidos por otra clase".
No encuentro contradicciones en los conceptos explicados en los tres enlaces que proporcionó.
Solo tenga en cuenta que el "cliente" es el implementador , en SOLID talk.
fuente
ISP se trata de aislar al cliente de saber más sobre el servicio de lo que necesita saber (protegerlo contra cambios no relacionados, por ejemplo). Su segunda definición es correcta. Para mi lectura, solo uno de esos tres artículos sugiere lo contrario ( el primero ) y es simplemente incorrecto. (Editar: No, no está mal, solo engañoso).
La primera definición está mucho más estrechamente vinculada al LSP.
fuente