Modello di Progettazione Chain of Responsibility in Java

Il design pattern Chain of Responsibility è uno dei pattern di progettazione comportamentale.

Design Pattern Chain of Responsibility

Il pattern di responsabilità viene utilizzato per ottenere un accoppiamento debole nel design del software, in cui una richiesta dal client viene passata a una catena di oggetti per elaborarla. Quindi, l’oggetto nella catena deciderà autonomamente chi elaborerà la richiesta e se la richiesta deve essere inviata all’oggetto successivo nella catena o meno.

Esempio di Design Pattern Chain of Responsibility in JDK

Vediamo l’esempio del pattern di catena di responsabilità in JDK e poi procederemo ad implementare un esempio di vita reale di questo pattern. Sappiamo che possiamo avere più blocchi catch in un blocco di codice try-catch. Ogni blocco catch è una sorta di processore per gestire quell’eccezione particolare. Quindi, quando si verifica un’eccezione nel blocco try, viene inviata al primo blocco catch per essere elaborata. Se il blocco catch non è in grado di elaborarla, inoltra la richiesta all’oggetto successivo nella catena, cioè al blocco catch successivo. Se anche l’ultimo blocco catch non è in grado di elaborarla, l’eccezione viene lanciata al di fuori della catena verso il programma chiamante.

Esempio di design pattern di catena di responsabilità

Uno dei grandi esempi del pattern Chain of Responsibility è il bancomat. L’utente inserisce l’importo da erogare e il bancomat eroga l’importo in termini di banconote definite come 50$, 20$, 10$ ecc. Se l’utente inserisce un importo che non è multiplo di 10, viene generato un errore. Utilizzeremo il pattern Chain of Responsibility per implementare questa soluzione. La catena elaborerà la richiesta nell’ordine mostrato nell’immagine sottostante. Si noti che è possibile implementare questa soluzione facilmente in un singolo programma, ma ciò aumenterebbe la complessità e la soluzione sarebbe fortemente accoppiata. Pertanto, creeremo una catena di sistemi di erogazione per erogare banconote da 50$, 20$ e 10$.

Pattern di Progettazione Chain of Responsibility – Classi Base e Interfaccia

Possiamo creare una classe Currency che memorizzerà l’importo da erogare e verrà utilizzata dalle implementazioni della catena. 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;
	}
}

L’interfaccia di base dovrebbe avere un metodo per definire il prossimo processore nella catena e il metodo che elaborerà la richiesta. La nostra interfaccia di erogazione del bancomat avrà un aspetto simile al seguente. DispenseChain.java

package com.journaldev.design.chainofresponsibility;

public interface DispenseChain {

	void setNextChain(DispenseChain nextChain);
	
	void dispense(Currency cur);
}

Modello di pattern Chain of Responsibilities – Implementazioni di Chain

Dobbiamo creare diverse classi di processore che implementeranno l’interfaccia DispenseChain e forniranno l’implementazione dei metodi di dispense. Poiché stiamo sviluppando il nostro sistema per lavorare con tre tipi di banconote – 50$, 20$ e 10$, creeremo tre implementazioni concrete. 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);
		}
	}

}

Il punto importante da notare qui è l’implementazione del metodo di dispense. Noterai che ogni implementazione cerca di elaborare la richiesta e in base all’importo potrebbe elaborarne una parte o interamente. Se una delle catene non è in grado di elaborarla completamente, invia la richiesta al successivo processore nella catena per elaborare la richiesta rimanente. Se il processore non è in grado di elaborare nulla, inoltra semplicemente la stessa richiesta alla catena successiva.

Modello di design Chain of Responsibilities – Creazione della catena

Questa è una fase molto importante e dovremmo creare la catena con cura, altrimenti un processore potrebbe non ricevere alcuna richiesta. Ad esempio, nella nostra implementazione se manteniamo la prima catena di processori come Dollar10Dispenser e poi Dollar20Dispenser, la richiesta non verrà mai inoltrata al secondo processore e la catena diventerà inutile. Ecco la nostra implementazione del distributore automatico di banconote per elaborare l’importo richiesto dall’utente. ATMDispenseChain.java

package com.journaldev.design.chainofresponsibility;

import java.util.Scanner;

public class ATMDispenseChain {

	private DispenseChain c1;

	public ATMDispenseChain() {
		// inizializzare la catena
		this.c1 = new Dollar50Dispenser();
		DispenseChain c2 = new Dollar20Dispenser();
		DispenseChain c3 = new Dollar10Dispenser();

		// impostare la catena di responsabilità
		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;
			}
			// elaborare la richiesta
			atmDispenser.c1.dispense(new Currency(amount));
		}

	}

}

Quando eseguiamo l’applicazione sopra, otteniamo un output simile al seguente.

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.

Diagramma delle classi del pattern design Chain of Responsibilities

Il nostro esempio di distributore automatico di banconote per il pattern design chain of responsibility si presenta come nell’immagine seguente.

Punti importanti del pattern design Chain of Responsibility

  • Il client non sa quale parte della catena elaborerà la richiesta e invierà la richiesta al primo oggetto della catena. Ad esempio, nel nostro programma ATMDispenseChain non si sa chi sta elaborando la richiesta per erogare l’importo inserito.
  • Ogni oggetto nella catena avrà la propria implementazione per elaborare la richiesta, sia completa o parziale o per inviarla all’oggetto successivo nella catena.
  • Ogni oggetto nella catena dovrebbe avere un riferimento all’oggetto successivo nella catena per inoltrare la richiesta, ciò è ottenuto tramite composizione in Java.
  • Creare la catena con attenzione è molto importante, altrimenti potrebbe verificarsi il caso che la richiesta non venga mai inoltrata a un particolare elaboratore o che non ci siano oggetti nella catena in grado di gestire la richiesta. Nella mia implementazione, ho aggiunto il controllo dell’importo inserito dall’utente per assicurarmi che venga elaborato completamente da tutti gli elaboratori, ma potremmo non controllarlo e lanciare un’eccezione se la richiesta raggiunge l’ultimo oggetto e non ci sono ulteriori oggetti nella catena a cui inoltrarla. Si tratta di una decisione di progettazione.
  • Il design pattern Chain of Responsibility è utile per ottenere un accoppiamento debole, ma comporta il compromesso di avere molte classi di implementazione e problemi di manutenzione se la maggior parte del codice è comune a tutte le implementazioni.

Esempi del modello di progettazione Chain of Responsibility in JDK

  • java.util.logging.Logger#log()
  • javax.servlet.Filter#doFilter()

Questo è tutto per il modello di progettazione Chain of Responsibility, spero ti sia piaciuto e che abbia chiarito la tua comprensione su questo modello di progettazione.

Source:
https://www.digitalocean.com/community/tutorials/chain-of-responsibility-design-pattern-in-java