El patrón de diseño de cadena de responsabilidad es uno de los patrones de diseño de comportamiento.
Patrón de diseño de cadena de responsabilidad
El patrón de cadena de responsabilidad se utiliza para lograr un acoplamiento flexible en el diseño de software donde una solicitud del cliente se pasa a una cadena de objetos para procesarlos. Luego, el objeto en la cadena decidirá por sí mismo quién procesará la solicitud y si es necesario enviar la solicitud al siguiente objeto en la cadena o no.
Ejemplo de patrón de diseño de cadena de responsabilidad en JDK
Veamos el ejemplo del patrón de cadena de responsabilidad en JDK y luego procederemos a implementar un ejemplo de la vida real de este patrón. Sabemos que podemos tener múltiples bloques catch en un código de bloque try-catch. Aquí cada bloque catch es como un procesador para procesar esa excepción en particular. Entonces, cuando ocurre una excepción en el bloque try, se envía al primer bloque catch para que lo procese. Si el bloque catch no puede procesarlo, reenvía la solicitud al siguiente objeto en la cadena, es decir, al siguiente bloque catch. Si incluso el último bloque catch no puede procesarlo, la excepción se lanza fuera de la cadena al programa que llama.
Ejemplo del Patrón de Diseño Cadena de Responsabilidad
Uno de los grandes ejemplos del patrón de Cadena de Responsabilidad es máquina dispensadora de cajeros automáticos. El usuario ingresa la cantidad a dispensar y la máquina dispensa la cantidad en términos de billetes de moneda definidos como 50$, 20$, 10$, etc. Si el usuario ingresa una cantidad que no es múltiplo de 10, arroja un error. Usaremos el patrón de Cadena de Responsabilidad para implementar esta solución. La cadena procesará la solicitud en el mismo orden que la imagen a continuación. Ten en cuenta que podemos implementar esta solución fácilmente en un solo programa, pero luego la complejidad aumentará y la solución estará fuertemente acoplada. Por lo tanto, crearemos una cadena de sistemas de dispensación para dispensar billetes de 50$, 20$ y 10$.
Patrón de Diseño de Cadena de Responsabilidad – Clases Base e Interfaz
Podemos crear una clase Currency
que almacenará la cantidad a dispensar y será utilizada por las implementaciones de la cadena. Currency.java
package com.journaldev.design.chainofresponsibility;
public class Currency {
private int amount;
public Currency(int amt){
this.amount=amt;
}
public int getAmount(){
return this.amount;
}
}
La interfaz base debería tener un método para definir el siguiente procesador en la cadena y el método que procesará la solicitud. Nuestra interfaz de Dispensación de ATM se verá así. DispenseChain.java
package com.journaldev.design.chainofresponsibility;
public interface DispenseChain {
void setNextChain(DispenseChain nextChain);
void dispense(Currency cur);
}
Patrón de Cadena de Responsabilidades – Implementaciones en Cadena
Necesitamos crear diferentes clases de procesadores que implementarán la interfaz DispenseChain
y proporcionarán la implementación de los métodos de dispensación. Dado que estamos desarrollando nuestro sistema para trabajar con tres tipos de billetes de moneda: 50$, 20$ y 10$, crearemos tres implementaciones concretas. Dollar50Dispenser.java
package com.journaldev.design.chainofresponsibility;
public class Dollar50Dispenser implements DispenseChain {
private DispenseChain chain;
@Override
public void setNextChain(DispenseChain nextChain) {
this.chain=nextChain;
}
@Override
public void dispense(Currency cur) {
if(cur.getAmount() >= 50){
int num = cur.getAmount()/50;
int remainder = cur.getAmount() % 50;
System.out.println("Dispensing "+num+" 50$ note");
if(remainder !=0) this.chain.dispense(new Currency(remainder));
}else{
this.chain.dispense(cur);
}
}
}
Dollar20Dispenser.java
package com.journaldev.design.chainofresponsibility;
public class Dollar20Dispenser implements DispenseChain{
private DispenseChain chain;
@Override
public void setNextChain(DispenseChain nextChain) {
this.chain=nextChain;
}
@Override
public void dispense(Currency cur) {
if(cur.getAmount() >= 20){
int num = cur.getAmount()/20;
int remainder = cur.getAmount() % 20;
System.out.println("Dispensing "+num+" 20$ note");
if(remainder !=0) this.chain.dispense(new Currency(remainder));
}else{
this.chain.dispense(cur);
}
}
}
Dollar10Dispenser.java
package com.journaldev.design.chainofresponsibility;
public class Dollar10Dispenser implements DispenseChain {
private DispenseChain chain;
@Override
public void setNextChain(DispenseChain nextChain) {
this.chain=nextChain;
}
@Override
public void dispense(Currency cur) {
if(cur.getAmount() >= 10){
int num = cur.getAmount()/10;
int remainder = cur.getAmount() % 10;
System.out.println("Dispensing "+num+" 10$ note");
if(remainder !=0) this.chain.dispense(new Currency(remainder));
}else{
this.chain.dispense(cur);
}
}
}
El punto importante a tener en cuenta aquí es la implementación del método de dispensación. Notarás que cada implementación intenta procesar la solicitud y, según la cantidad, puede procesar una parte o la totalidad de la misma. Si una de las cadenas no puede procesarla completamente, envía la solicitud al siguiente procesador en la cadena para que procese la solicitud restante. Si el procesador no puede procesar nada, simplemente reenvía la misma solicitud a la siguiente cadena.
Patrón de Diseño de Cadena de Responsabilidades – Creación de la Cadena
Este es un paso muy importante y debemos crear la cadena cuidadosamente, de lo contrario, un procesador podría no recibir ninguna solicitud en absoluto. Por ejemplo, en nuestra implementación si mantenemos la primera cadena de procesadores como Dollar10Dispenser
y luego Dollar20Dispenser
, entonces la solicitud nunca será enviada al segundo procesador y la cadena se volverá inútil. Aquí está nuestra implementación del dispensador de cajero automático para procesar el monto solicitado por el usuario. ATMDispenseChain.java
package com.journaldev.design.chainofresponsibility;
import java.util.Scanner;
public class ATMDispenseChain {
private DispenseChain c1;
public ATMDispenseChain() {
// inicializar la cadena
this.c1 = new Dollar50Dispenser();
DispenseChain c2 = new Dollar20Dispenser();
DispenseChain c3 = new Dollar10Dispenser();
// establecer la cadena de responsabilidad
c1.setNextChain(c2);
c2.setNextChain(c3);
}
public static void main(String[] args) {
ATMDispenseChain atmDispenser = new ATMDispenseChain();
while (true) {
int amount = 0;
System.out.println("Enter amount to dispense");
Scanner input = new Scanner(System.in);
amount = input.nextInt();
if (amount % 10 != 0) {
System.out.println("Amount should be in multiple of 10s.");
return;
}
// procesar la solicitud
atmDispenser.c1.dispense(new Currency(amount));
}
}
}
Al ejecutar la aplicación anterior, obtenemos una salida como la siguiente.
Enter amount to dispense
530
Dispensing 10 50$ note
Dispensing 1 20$ note
Dispensing 1 10$ note
Enter amount to dispense
100
Dispensing 2 50$ note
Enter amount to dispense
120
Dispensing 2 50$ note
Dispensing 1 20$ note
Enter amount to dispense
15
Amount should be in multiple of 10s.
Diagrama de Clase del Patrón de Diseño de Cadena de Responsabilidades
Nuestro ejemplo de dispensador de cajero automático de implementación del patrón de diseño de cadena de responsabilidad se ve como la imagen a continuación.
Puntos Importantes del Patrón de Diseño de Cadena de Responsabilidades
- El cliente no sabe qué parte de la cadena procesará la solicitud y enviará la solicitud al primer objeto en la cadena. Por ejemplo, en nuestro programa, ATMDispenseChain no sabe quién está procesando la solicitud para dispensar la cantidad ingresada.
- Cada objeto en la cadena tendrá su propia implementación para procesar la solicitud, ya sea completa, parcial o para enviarla al siguiente objeto en la cadena.
- Cada objeto en la cadena debe tener una referencia al siguiente objeto en la cadena para reenviar la solicitud; esto se logra mediante composición en Java.
- Crear la cadena cuidadosamente es muy importante, de lo contrario, podría darse el caso de que la solicitud nunca se reenvíe a un procesador en particular o que no haya objetos en la cadena que puedan manejar la solicitud. En mi implementación, he agregado la verificación para la cantidad ingresada por el usuario para asegurarme de que se procese completamente por todos los procesadores, pero podríamos no verificarlo y lanzar una excepción si la solicitud llega al último objeto y no hay más objetos en la cadena para reenviar la solicitud. Esta es una decisión de diseño.
- El patrón de diseño Cadena de Responsabilidad es bueno para lograr un acoplamiento débil, pero tiene el inconveniente de tener muchas clases de implementación y problemas de mantenimiento si la mayor parte del código es común en todas las implementaciones.
Ejemplos del Patrón de Cadena de Responsabilidad en JDK
- java.util.logging.Logger#log()
- javax.servlet.Filter#doFilter()
Es todo para el patrón de diseño Cadena de Responsabilidad. Espero que te haya gustado y que haya aclarado tu comprensión sobre este patrón de diseño.
Source:
https://www.digitalocean.com/community/tutorials/chain-of-responsibility-design-pattern-in-java