Segregación de Interfaces (ISP)

No todo debe de ir a parar al mismo cubo

El principio SOLID que os ocupa hoy es el llamado Principio de Segregación de Interfaces, a partir de ahora ISP.

Descripción

El ISP fue enunciado por Bob C. Martin cuando trabajaba en Xerox y estaban programando un nuevo modelo de impresora, que además de imprimir, grapaba y enviaba faxes. Durante el desarrollo del software para esta impresora, Martin se dio cuenta de que una clase en concreto (la clase Job) estaba engordando excesivamente por un exceso de métodos, ya que casi todas las tareas (imprimir, grapar, enviar…) hacían uso de ella. Lo peor de este diseño es que cada tarea tenía acceso a métodos que no necesitaba para nada (por ejemplo, la tarea de grapar podía acceder a los métodos que usaría imprimir).

La solución de Martin pasó por definir una serie de interfaces para cada tipo de trabajo: StapleJobInterface, PrintJobInterface y FaxJobInterface. La clase Job implementaría todas estas interfaces, pero en los métodos de las clases Print, Staple y Fax se les pasaría como parámetro un objeto que implementase solo la interfaz adecuada para su trabajo, quedando el resto de métodos ocultos al servicio.

Otra manera de interpretar la solución hubiese sido crear las interfaces, sacar los métodos de la clase Job y crear nuevas clases que extiendan de Job e implementen cada una una interfaz, creando los objetos PrintJob, FaxJob y StapleJob:

Esta solución, en mi opinión, es más coherente con el resto de principios SOLID, especialmente con el SRP, ya que distribuye las responsabilidades en distintas clases.

Con estos antecedentes, ¿cómo podemos definir el ISP? Wikipedia lo hace muy bien es definición:

El principio de segregación de la interfaz (…) establece que los clientes de un programa dado sólo deberían conocer de éste aquellos métodos que realmente usan, y no aquellos que no necesitan usar.

Esto implica que nuestras interfaces sean pequeñas, adecuadas para el rol que quieren aportar a la clase que las implementa, por lo que también son llamadas interfaces de rol.

Las interfaces, como ya sabemos, definen comportamiento, pero no su implementación. Cuando usamos mecanismos de composición, podemos implementar tantas interfaces como necesitemos en nuestra clase, al contrario del mecanismo de herencia que solo nos permite heredar de una única clase, y es por esto que la composición nos da una flexibilidad extra: la posibilidad de tener distintas implementaciones para una misma funcionalidad.

¿Cómo detectamos que no estamos cumpliendo con el ISP?

La forma más sencilla de detectar que no cumplimos con el ISP es observando nuestras interfaces y su tamaño.

  • ¿Tiene demasiados métodos y algunos de ellos tendría sentido separarlos en una nueva interfaz?
  • Cada vez que una clase nueva implementa una interfaz ¿te cuesta dejar métodos sin implementar o lanzando una excepción?
  • El rasgo que querías modelar con una interfaz, ¿se podría dividir en varias?
  • ¿Tienes una clase que se usa para distintas tareas y estas tareas tienen acceso al resto de métodos de la clase?

Si has contestado SI a al menos una de las anteriores preguntas, probablemente estés antes una violación del ISP.

¿Cómo puedo cumplir con el ISP?

Si aún no lo estás incumpliendo, sigue como hasta ahora, no dejes que tus interfaces se vuelvan gigantescas, modela los roles con interfaces pequeñas y asegúrate de que las clases que las implementan no tienen problemas para hacerlo.

Si ya has detectado una o varias violaciones, es momento de ponerle remedio: el patrón adapter puede ser de gran ayuda en estos casos, convirtiendo una interfaz gigante en una más pequeña usando adaptadores para la transición.

Hasta la próxima!