¿Existe un principio de interfaz de "pedir solo lo que necesita"?

9

Me he acostumbrado a utilizar un principio para diseñar y consumir interfaces que dice básicamente: "pida solo lo que necesita".

Por ejemplo, si tengo un montón de tipos que se pueden eliminar, haré una Deletableinterfaz:

interface Deletable {
   void delete();
}

Entonces puedo escribir una clase genérica:

class Deleter<T extends Deletable> {
   void delete(T t) {
      t.delete();
   }
}

En otras partes del código, siempre pediré la menor responsabilidad posible para cumplir con las necesidades del código del cliente. Entonces, si solo necesito eliminar un File, aún pediré un Deletable, no un File.

¿Es este principio de conocimiento común y ya tiene un nombre aceptado? ¿Es controvertido? ¿Se discute en los libros de texto?

glenviewjeff
fuente
1
Acoplamiento flojo tal vez? O interfaces estrechas?
tdammers

Respuestas:

16

Creo que esto se refiere a lo que Robert Martin llama el Principio de segregación de interfaz . Las interfaces se separan en pequeñas y concisas para que los consumidores (clientes) solo tengan que conocer los métodos que les interesan. Puedes ver más en SOLID .

Vadim
fuente
4

Para ampliar la muy buena respuesta de Vadim, responderé la pregunta "¿es controvertido" con "no, no realmente".

En general, la segregación de la interfaz es algo bueno, al reducir el número total de "razones para cambiar" de los diversos objetos involucrados. El principio básico es que, cuando se debe cambiar una interfaz con varios métodos, por ejemplo, para agregar un parámetro a uno de los métodos de la interfaz, todos los consumidores de la interfaz deben al menos volver a compilarse, incluso si no utilizaron el método que cambió. "¡Pero es solo una recompilación!", Te escucho decir; eso puede ser cierto, pero tenga en cuenta que, por lo general, cualquier cosa que recompile debe eliminarse como parte de un parche de software, sin importar cuán significativo sea el cambio en el binario. Estas reglas se conceptualizaron originalmente a principios de los años 90, cuando la estación de trabajo de escritorio promedio era menos potente que el teléfono en su bolsillo, el marcado de 14.4k baudios era increíble, y 3.5 "1.44MB" disquetes "eran los principales medios extraíbles. Incluso en la era actual de 3G / 4G, los usuarios de Internet inalámbrico a menudo tienen planes de datos con límites, por lo que al lanzar una actualización, cuantos menos binarios se deben descargar, mejor.

Sin embargo, como todas las buenas ideas, la segregación de la interfaz puede ir mal si se implementa incorrectamente. En primer lugar, existe la posibilidad de que al segregar las interfaces mientras se mantiene el objeto que implementa esas interfaces (cumpliendo las dependencias) relativamente sin cambios, puede terminar con una "Hidra", un pariente del antipatrón "Objeto de Dios" donde el la naturaleza omnisciente y todopoderosa del objeto está oculta para los dependientes por las interfaces estrechas. Terminas con un monstruo de muchas cabezas que es al menos tan difícil de mantener como lo sería el Objeto de Dios, más la sobrecarga de mantener todas sus interfaces. No hay una gran cantidad de interfaces que no debe exceder, pero cada interfaz que implemente en un solo objeto debe ser precedida respondiendo la pregunta: "¿Esta interfaz contribuye al objeto '

En segundo lugar, una interfaz por método puede no ser necesaria, a pesar de lo que SRP pueda decirle. Puede terminar con "código de ravioles"; tantos trozos del tamaño de un bocado que es difícil de rastrear para descubrir exactamente dónde suceden realmente las cosas. Tampoco es necesario dividir una interfaz con dos métodos si todos los usuarios actuales de esa interfaz necesitan ambos métodos. Incluso si una de las clases dependientes solo necesita uno de los dos métodos, generalmente es aceptable no dividir la interfaz si sus métodos conceptualmente tienen una cohesión muy alta (buenos ejemplos son "métodos antonímicos" que son exactamente opuestos entre sí).

La segregación de interfaz debe basarse en las clases que dependen de la interfaz:

  • Si solo hay una clase dependiente de la interfaz, no la segregue. Si la clase no usa uno o más de los métodos de interfaz, y es el único consumidor de la interfaz, es probable que no debas haber expuesto esos métodos en primer lugar.

  • Si hay más de una clase que depende de la interfaz y todos los dependientes usan todos los métodos de la interfaz, no los segregue; si debe cambiar la interfaz (para agregar un método o cambiar una firma), todos los consumidores actuales se verán afectados por el cambio, ya sea que lo separe o no (aunque si agrega un método que al menos un dependiente no necesitará, considere cuidadosamente si el cambio debe implementarse como una nueva interfaz, posiblemente heredada de la existente).

  • Si hay más de una clase dependiente de la interfaz, y no utilizan los mismos métodos, es un candidato para la segregación. Mire la "coherencia" de la interfaz; ¿Todos los métodos promueven un único objetivo de programación muy específico? Si puede identificar más de un propósito central para la interfaz (y sus implementadores), considere dividir las interfaces a lo largo de esas líneas para crear interfaces más pequeñas con menos "razones para cambiar".

KeithS
fuente
También vale la pena señalar que la segregación de la interfaz puede ser buena y elegante si se usa un lenguaje / sistema OOP que puede permitir que el código especifique una combinación precisa de interfaces, pero al menos en .NET pueden causar fuertes dolores de cabeza, ya que no hay decente forma de especificar una colección de "cosas que implementan IFoo e IBar, pero que de otra manera podrían no tener nada en común".
supercat
Los parámetros de tipo genérico se pueden definir con criterios que incluyen la implementación de múltiples interfaces, pero tiene razón en que las expresiones que requieren un tipo estático generalmente no pueden admitir la especificación de más de una. Si existe la necesidad de que un tipo estático implemente tanto IFoo como IBar, y usted controla ambas interfaces, puede ser una buena idea implementarlo IBaz : IFoo, IBary requerirlo.
KeithS
Si el código del cliente puede necesitar algo que se pueda usar como IFooy IBar, definir una composición IFooBarpuede ser una buena idea, pero si las interfaces se dividen finamente, es fácil terminar requiriendo docenas de tipos de interfaz distintos. Tenga en cuenta las siguientes características que las colecciones pueden tener: enumerar, contar recuento, leer el enésimo elemento, escribir el enésimo elemento, insertar antes del enésimo elemento, eliminar el enésimo elemento, nuevo elemento (ampliar la colección y devolver el índice del nuevo espacio) y agregar. Nueve métodos: ECRWIDNA. Probablemente podría describir docenas de tipos que naturalmente admitirían muchas combinaciones diferentes.
supercat
Las matrices, por ejemplo, serían compatibles con ECRW. Un arraylist sería compatible con ECRWIDNA. Una lista segura para subprocesos podría ser compatible con ECRWNA [aunque A generalmente solo sería útil para rellenar previamente la lista]. Un contenedor de matriz de solo lectura podría admitir ECR. Una interfaz de lista covariante podría admitir ECRD. Una interfaz no genérica podría proporcionar compatibilidad C o CD con seguridad de tipo. Si Swap fuera una opción, algunos tipos podrían admitir CS pero no D (por ejemplo, matrices) mientras que otros admitirían CDS. Intentar definir distintos tipos de interfaz para cada combinación necesaria de habilidades sería una pesadilla.
supercat
Ahora imagine que uno quiere la capacidad de envolver una colección con un objeto que puede hacer todo lo que la colección puede hacer, pero que registra cada transacción. ¿Cuántos envoltorios necesitaría uno? Si todas las colecciones heredaran de una interfaz común que incluyera propiedades para identificar sus habilidades, un envoltorio sería suficiente. Sin embargo, si todas las interfaces son distintas, se necesitarían docenas.
Supercat