Principio de segregación de interfaces en Java

1. Introducción

En este tutorial, discutiremos el principio de segregación de interfaces, uno de los principios SOLID. Al representar la "I" en "SOLID", la segregación de interfaces simplemente significa que debemos dividir las interfaces más grandes en más pequeñas.

De este modo, se garantiza que las clases de implementación no necesitan implementar métodos no deseados.

2. Principio de segregación de interfaces

Este principio fue definido por primera vez por Robert C. Martin como: “ No se debe obligar a los clientes a depender de interfaces que no utilizan ”.

El objetivo de este principio es reducir los efectos secundarios del uso de interfaces más grandes dividiendo las interfaces de la aplicación en otras más pequeñas . Es similar al principio de responsabilidad única, donde cada clase o interfaz tiene un único propósito.

El diseño preciso de la aplicación y la abstracción correcta son la clave detrás del principio de segregación de interfaces. Aunque tomará más tiempo y esfuerzo en la fase de diseño de una aplicación y podría aumentar la complejidad del código, al final, obtenemos un código flexible.

Veremos algunos ejemplos en las secciones posteriores donde tenemos una violación del principio, y luego solucionaremos el problema aplicando el principio correctamente.

3. Ejemplo de interfaz e implementación

Veamos una situación en la que tenemos una interfaz de pago utilizada por una implementación de BankPayment :

public interface Payment { void initiatePayments(); Object status(); List getPayments(); }

Y la implementación:

public class BankPayment implements Payment { @Override public void initiatePayments() { // ... } @Override public Object status() { // ... } @Override public List getPayments() { // ... } }

Para simplificar, ignoremos la implementación empresarial real de estos métodos.

Esto es muy claro: hasta ahora, la clase de implementación BankPayment necesita todos los métodos en la interfaz de Pago . Por tanto, no viola el principio.

4. Contaminación de la interfaz

Ahora, a medida que avanzamos en el tiempo y aparecen más funciones, es necesario agregar un servicio de LoanPayment . Este servicio también es una forma de Pago pero tiene algunas operaciones más.

Para desarrollar esta nueva función, agregaremos los nuevos métodos a la interfaz de pago :

public interface Payment { // original methods ... void intiateLoanSettlement(); void initiateRePayment(); }

A continuación, tendremos la implementación de LoanPayment :

public class LoanPayment implements Payment { @Override public void initiatePayments() { throw new UnsupportedOperationException("This is not a bank payment"); } @Override public Object status() { // ... } @Override public List getPayments() { // ... } @Override public void intiateLoanSettlement() { // ... } @Override public void initiateRePayment() { // ... } }

Ahora, dado que la interfaz de Pago ha cambiado y se agregaron más métodos, todas las clases de implementación ahora tienen que implementar los nuevos métodos. El problema es que implementarlos no es deseado y podría provocar muchos efectos secundarios. Aquí, la clase de implementación LoanPayment tiene que implementar initiatePayments () sin ninguna necesidad real de esto. Y así, se viola el principio.

Entonces, qué sucede con nuestra clase BankPayment :

public class BankPayment implements Payment { @Override public void initiatePayments() { // ... } @Override public Object status() { // ... } @Override public List getPayments() { // ... } @Override public void intiateLoanSettlement() { throw new UnsupportedOperationException("This is not a loan payment"); } @Override public void initiateRePayment() { throw new UnsupportedOperationException("This is not a loan payment"); } }

Tenga en cuenta que la implementación de BankPayment ahora ha implementado los nuevos métodos. Y como no los necesita y no tiene lógica para ellos, solo está lanzando una UnsupportedOperationException . Aquí es donde comenzamos a violar el principio.

En la siguiente sección, veremos cómo podemos resolver este problema.

5. Aplicación del principio

En la última sección, hemos contaminado intencionalmente la interfaz y violado el principio. En esta sección, veremos cómo agregar la nueva función para el pago de préstamos sin violar el principio.

Analicemos la interfaz para cada tipo de pago. La situación actual:

Observe en el diagrama de clases, y refiriéndose a las interfaces en la sección anterior, que los métodos status () y getPayments () son necesarios en ambas implementaciones. Por otro lado, initiatePayments () solo se requiere en BankPayment , y los métodos initiateLoanSettlement () e initiateRePayment () son solo para LoanPayment .

Con eso ordenado, separemos las interfaces y apliquemos el principio de segregación de interfaces. Por lo tanto, ahora tenemos una interfaz común:

public interface Payment { Object status(); List getPayments(); }

Y dos interfaces más para los dos tipos de pagos:

public interface Bank extends Payment { void initiatePayments(); }
public interface Loan extends Payment { void intiateLoanSettlement(); void initiateRePayment(); }

Y las respectivas implementaciones, comenzando con BankPayment :

public class BankPayment implements Bank { @Override public void initiatePayments() { // ... } @Override public Object status() { // ... } @Override public List getPayments() { // ... } }

Y finalmente, nuestra implementación de LoanPayment revisada :

public class LoanPayment implements Loan { @Override public void intiateLoanSettlement() { // ... } @Override public void initiateRePayment() { // ... } @Override public Object status() { // ... } @Override public List getPayments() { // ... } }

Ahora, revisemos el nuevo diagrama de clases:

Como podemos ver, las interfaces no violan el principio. Las implementaciones no tienen que proporcionar métodos vacíos. Esto mantiene el código limpio y reduce la posibilidad de errores.

6. Conclusión

En este tutorial, analizamos un escenario simple, donde primero nos desviamos de seguir el Principio de Segregación de Interfaces y vimos los problemas que causaba esta desviación. Luego mostramos cómo aplicar el principio correctamente para evitar estos problemas.

En caso de que se trate de interfaces heredadas contaminadas que no podemos modificar, el patrón del adaptador puede resultar útil.

El Principio de Segregación de Interfaces es un concepto importante al diseñar y desarrollar aplicaciones. Adherirse a este principio ayuda a evitar interfaces infladas con múltiples responsabilidades. Con el tiempo, esto también nos ayuda a seguir el principio de responsabilidad única.

Como siempre, el código está disponible en GitHub.