El principio de segregación de interfaz, conocido como ISP por sus siglas en inglés, es uno de los pilares fundamentales dentro de la metodología SOLID para el diseño de software. Este principio nos invita a diseñar interfaces pequeñas y específicas, evitando que los clientes dependan de métodos que no utilizan. Así, conseguimos un código más limpio, fácil de mantener y extender.
Para entender mejor este principio, imaginemos que estamos modelando una impresora multifunción. Esta impresora puede realizar varias tareas: imprimir documentos, escanearlos y enviar faxes. Si creamos una única interfaz que incluya métodos para imprimir, escanear y enviar fax, cualquier clase que implemente esta interfaz deberá definir los tres métodos, aunque no todos sean necesarios para su funcionalidad concreta.
Por ejemplo, si queremos crear una clase que solo imprima informes, esta clase tendría que depender de una interfaz que también incluye métodos para escanear y enviar fax, aunque no los utilice. Esto genera problemas a la hora de modificar el código, ya que la clase depende de métodos innecesarios, y complica la creación de pruebas unitarias. Para hacer un mock de la impresora multifunción en un test, tendríamos que implementar métodos que no nos interesan, como escanear o enviar fax, lo que añade complejidad y ruido al código de prueba.
Además, si tenemos una impresora sencilla que solo imprime, no podríamos usarla directamente en un sistema que espera una impresora multifunción, porque esta interfaz exige implementar métodos que la impresora sencilla no soporta. Esto suele llevar a implementar métodos vacíos o que lanzan excepciones, lo que es una señal clara de que la interfaz está demasiado cargada.
La solución a este problema es dividir la interfaz grande en varias interfaces más pequeñas y específicas. Por ejemplo, podemos tener una interfaz Impresora con un método imprimir, otra interfaz Escaner con un método escanear, y una interfaz Fax con un método para enviar documentos por fax. De esta forma, una clase puede implementar solo las interfaces que realmente necesita.
Si queremos representar una impresora multifunción, podemos hacer que implemente las tres interfaces. Gracias a la herencia múltiple o a mecanismos similares como los traits en algunos lenguajes, una clase puede comportarse como impresora, escáner y fax al mismo tiempo. Así, los métodos que reciben estas interfaces como parámetros pueden depender solo de la funcionalidad que necesitan, sin cargar con métodos innecesarios.
Este enfoque es común en muchos lenguajes modernos. Por ejemplo, en Go encontramos interfaces como Writer o Reader que definen un único método, y que pueden combinarse para formar interfaces más complejas. En Java o .NET, las interfaces pequeñas y específicas facilitan la implementación y el mantenimiento, como ocurre con las interfaces funcionales de un solo método o con las interfaces AutoCloseable en Java.
Al aplicar el principio de segregación de interfaz, facilitamos la creación de mocks para pruebas, ya que solo necesitamos implementar los métodos relevantes. También evitamos tener métodos vacíos o que lanzan excepciones en implementaciones que no los necesitan, lo que mejora la coherencia y claridad del código.
En definitiva, se trata de segregar interfaces grandes en partes más pequeñas y específicas, y combinarlas cuando sea necesario, para que cada cliente dependa únicamente de los métodos que realmente utiliza. Así conseguimos un diseño más limpio, modular y fácil de mantener.