Dos definiciones contradictorias del Principio de Segregación de Interfaz, ¿cuál es la correcta?

13

Al leer artículos sobre ISP, parece haber dos definiciones contradictorias de ISP:

Según la primera definición (consulte 1 , 2 , 3 ), el ISP establece que las clases que implementan la interfaz no deben ser forzadas a implementar funcionalidades que no necesitan. Por lo tanto, la interfaz de grasa IFat

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

class MyClass: IFat
{ ... }

se debe dividir en interfaces más pequeñas ISmall_1 y ISmall_2

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

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

class MyClass:ISmall_2
{ ... }

ya que de esta manera mi MyClass es capaz de implementar solo los métodos que necesita ( D() y C() ), sin ser forzado a proporcionar implementaciones ficticias para A() , B() y C() :

Pero de acuerdo con la segunda definición (consulte 1 , 2 , respuesta de Nazar Merza ), ISP declara que los métodos de llamada MyClient en MyService no deberían ' t Esté al tanto de los métodos en MyService que no necesita. En otras palabras, si MyClient solo necesita la funcionalidad de C() y D() , entonces 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();

deberíamos segregar los métodos MyService's 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 definición anterior, el objetivo del ISP es " facilitar la vida de las clases implementando la interfaz IFat ", mientras que con el último el objetivo del ISP es " hacer el La vida de los clientes llama a los métodos de MyService más fáciles ".

¿Cuál de las dos definiciones diferentes de ISP es realmente correcta?

@MARJAN VENEMA

1.

  

Entonces, cuando vas a dividir IFat en una interfaz más pequeña, que   Los métodos terminan en los que ISmallinterface debe decidirse en función de cómo   cohesionados son los miembros.

Aunque 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é con el ISP que deberíamos agrupar dentro de la misma interfaz aquellos métodos que necesitan los clientes en particular, incluso si eso significa dejar fuera de esa interfaz aquellos métodos que deberían, por el bien de la cohesión, también ser incluidos dentro de esa misma interfaz.

Por lo tanto, si hubiera muchos clientes que solo necesitarían llamar a CutGreens , pero no también a GrillMeat , entonces para cumplir con el patrón de ISP solo deberíamos poner CutGreens dentro de ICook , pero no también GrillMeat , a pesar de que los dos métodos son altamente cohesivos?

2.

  

Creo que su confusión proviene de un supuesto oculto en el   Primera definición: que las clases implementadoras ya están siguiendo.   El principio de responsabilidad única.

Al "implementar clases que no siguen a SRP", ¿se refiere a las clases que implementan IFat o a las clases que implementan ISmall_1 / ISmall_2 ? Supongo que te refieres a las clases que implementan IFat ? Si es así, ¿por qué asumes que no siguen SRP?

gracias

    
pregunta EdvRusj 21.06.2013 - 18:41

3 respuestas

5

Ambos son correctos

La forma en que lo leí, el propósito de 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. Se pretende que ambas definiciones eviten las interfaces "jack-of-all-trad-master-of-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 cohesivos. Se complementan entre sí. La segregación de interfaz asegura que las interfaces sean pequeñas, enfocadas y altamente cohesivas. Seguir el principio de responsabilidad única garantiza que las clases sean pequeñas, enfocadas y altamente cohesivas.

La primera definición que mencionas se centra en los implementadores, la segunda en los clientes. Que, a diferencia de @ user61852, considero que son los usuarios / llamadores de la interfaz, no los implementadores.

Creo que su confusión proviene de un supuesto oculto 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 que llaman a la interfaz, es una mejor manera de alcanzar el objetivo deseado.

Segregando

En tu pregunta indicas:

  

ya que de esta manera mi MyClass puede implementar solo los métodos que   necesita (D () y C ()), sin ser forzado a proporcionar también dummy   implementaciones para A (), B () y C ():

Pero eso está dando la vuelta al mundo.

  • Una clase que implementa una interfaz no dicta lo que necesita en la interfaz que está implementando.
  • Las interfaces dictan qué métodos debe proporcionar una clase de implementación.
  • Las personas que llaman a una interfaz realmente son las que dictan qué funcionalidad necesitan la interfaz para proporcionarles y, por lo tanto, lo que debe proporcionar un implementador.

Entonces, cuando vaya a dividir IFat en una interfaz más pequeña, qué métodos terminarán en la cual la interfaz ISmall debería decidirse según la cohesión de los miembros.

Considera esta interfaz:

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

¿Qué métodos pondrías en ICook y por qué? ¿Pondría a CleanSink junto con GrillMeat solo porque tiene una clase que hace exactamente eso y un par de otras cosas pero nada como ninguno de los otros métodos? ¿O lo dividirías en dos interfaces más cohesivas, como:

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

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

Nota de declaración de interfaz

La definición de una interfaz debería estar preferiblemente en su propia unidad, pero si es absolutamente necesario que viva con la persona que llama o con el implementador, debería estar con la persona que llama. De lo contrario, la persona que llama obtiene una dependencia inmediata del implementador, lo que está derrotando el propósito de las interfaces por completo. Consulte también: Declarar la interfaz en el mismo archivo que la clase base, ¿es una buena práctica? en Programadores y < a href="https://stackoverflow.com/q/5840219/11225"> ¿Por qué deberíamos colocar interfaces con clases que las usan en lugar de aquellas que las implementan? en StackOverflow.

    
respondido por el Marjan Venema 21.06.2013 - 21:38
13

Confunde la palabra "cliente" como se usa en los documentos de la Banda de los Cuatro con un "cliente" como en el consumidor de un servicio.

Un "cliente", tal como pretenden las definiciones de Gang of Four, 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 deben ser forzados a implementar interfaces que no usan" no tendrían sentido ya que "los clientes "(como en los consumidores) no implementan nada. La frase solo tiene sentido cuando ve "cliente" como "implementador".

Si "cliente" significa una clase que "consume" (llama) los métodos de otra clase que implementa la gran interfaz, al llamar a los dos métodos que te interesan e ignorar el resto, sería suficiente para mantenerte desconectado El 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 se preocupa por un conjunto de métodos relacionados.

También apunta a tener la menor cantidad de acoplamiento posible para que los cambios realizados en un lugar causen el menor impacto. Al segregar las interfaces, se reduce el acoplamiento.

Los problemas aparecen cuando la interfaz hace demasiado y tiene métodos que deben dividirse en varias interfaces en lugar de solo una.

Ambos ejemplos de código están bien . Es solo que en el segundo se supone 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 proporcionaste.

Solo tenga en cuenta que "cliente" es el implementador , en conversación SOLID.

    
respondido por el Tulains Córdova 21.06.2013 - 19:44
5

ISP tiene que ver con aislar al cliente de saber más sobre el servicio que lo que necesita saber (protegerlo contra cambios no relacionados, por ejemplo). Su segunda definición es correcta. A mi lectura, solo uno de esos tres artículos sugiere lo contrario ( el primero ) y es simplemente un error. (Edición: No, no está mal, solo es engañoso).

La primera definición está mucho más estrechamente vinculada al LSP.

    
respondido por el pdr 21.06.2013 - 18:44

Lea otras preguntas en las etiquetas