Il Pattern Chain of Responsibility

  



Il Chain of Responsibility è un design pattern comportamentale che consente a un oggetto di passare una richiesta lungo una catena di gestori. Ogni gestore può elaborare la richiesta o passarla al successivo nella catena. Questo pattern è utile per disaccoppiare mittenti e destinatari, rendendo il sistema più flessibile e scalabile.

🔗 Ti piace Techelopment? Dai un’occhiata al sito per tutti i dettagli!


Famiglia di Pattern: Pattern Comportamentali

Il pattern Chain of Responsibility appartiene alla famiglia dei Pattern Comportamentali. I pattern comportamentali si concentrano su come gli oggetti interagiscono e comunicano tra loro. Aiutano a definire come le responsabilità vengono distribuite tra le classi e come gli oggetti cooperano mantenendo un basso accoppiamento. Altri pattern di questa famiglia includono Observer, Strategy, Command e Template Method.


Diagramma



Come Funziona il Pattern Chain of Responsibility

Il pattern Chain of Responsibility funziona creando una lista collegata di gestori, dove ogni gestore può elaborare una richiesta o passarla al successivo. Questo permette a più oggetti di elaborare una richiesta senza che il mittente debba sapere quale di essi la gestirà. Ciò consente anche di separare le logiche di implementazione creando disaccoppiamento e mantendo il codice più modulare e manutenibile.

Vediamo un esempio semplice in JavaScript per comprenderne meglio il funzionamento:

// Handler Interface
class ValidationHandler {
    setNextHandler(handler) {
        this.nextHandler = handler;
    }

    handleRequest(regex) {
        throw new Error("This method should be overridden!");
    }
}

// Concrete Handlers
class Validation1 extends ValidationHandler {
    handleRequest(request) {
        if (request.message && request.message !== "") {
            console.log("Not empty validation OK")            
            if (this.nextHandler) {
                this.nextHandler.handleRequest(request);
            }
        }
    }
}

class Validation2 extends ValidationHandler {
   
    handleRequest(request) {
        const regex = /[0-9]+/;

        if (!regex.test(request.message)) {
            console.log("Validation not digit OK");
             
            if (this.nextHandler) {
                this.nextHandler.handleRequest(request);
            }
        }
    }
}

class Validation3 extends ValidationHandler {
    handleRequest(request) {
        const MINIMUM_LENGTH = 10;
        if (request.message.length > MINIMUM_LENGTH) {
            console.log(`Validation minimum length ${MINIMUM_LENGTH} OK`);
            if (this.nextHandler) {
                this.nextHandler.handleRequest(request);
            }
        }
    }
}

// Execution (Client) Code
const validation1 = new Validation1();
const validation2 = new Validation2();
const validation3 = new Validation3();

validation1.setNextHandler(validation2);
validation2.setNextHandler(validation3);

const messageToValidate = { message: "Hello, World!" }
validation1.handleRequest(messageToValidate);

Come funziona:

  • Il primo gestore (Validation1) verifica che il messaggio non sia vuoto prima di passarlo al gestore successivo.

  • Il secondo gestore (Validation2) controlla che il messaggio non contenga cifre; se è valido, lo inoltra.

  • Il terzo gestore (Validation3) verifica che il messaggio abbia una lunghezza minima prima di passarlo oltre.

  • Questo approccio garantisce che ogni validazione venga applicata in sequenza e che la richiesta sia completamente validata solo se supera tutti i controlli.

In questo modo possiamo aggiungere nuove validazioni semplicemente implementando una nuova classe che estende ValidationHandler e implementando il metodo handleRequest con il codice della validazione da eseguire. Basterà poi aggiungere un oggetto della nuova classe alla catena (da qui il nome Chain of Responsability): validation3.setNextHandler(validation4).

Un esempio più concreto: Implementare un Wizard in JavaScript

Consideriamo ora un caso d'uso pratico per il pattern Chain of Responsibility: la gestione di un form multi-step o di un wizard. Questo pattern ci permette di gestire dinamicamente gli step del form modificando il nextHandler senza dover riscrivere l'intera logica di gestione degli step. Questa flessibilità facilita l'aggiunta, la rimozione o la riorganizzazione degli step, mantenendo il codice pulito e modulare.

Il wizard consiste nei seguenti step:
  1. Insert your personal data: inserimento dei dati personali di un utente

  2. Upload your photo: caricamento di una foto 

  3. Confirm data: conferma dei dati inseriti

  4. Outcome: esito dell'operazione

Ogni step sarà un gestore nella catena, elaborando la richiesta o passandola avanti.

class WizardStep {
    setNext(step) {
        this.next = step;
        return step;
    }
   
    handle(request) {
        throw new Error("This method should be overridden!");
    }

    process(request) {
        if (this.next) {
            return this.next.handle(request);
        }
    }
}

class PersonalDataStep extends WizardStep {
    handle(request) {
        console.log("Step 1: Insert your personal data");

        //...code to manage personal data collection
       
        request.personalDataInserted = true;
        super.process(request);
    }
}

class UploadPhotoStep extends WizardStep {
    handle(request) {
        if (!request.personalDataInserted) {
            console.log("Error: Personal data must be inserted before uploading a photo.");
            return;
        }
        console.log("Step 2: Load your photo");
         
        //...manage photo uploading

        request.photoUploaded = true;
        super.process(request);
    }
}

class ConfirmDataStep extends WizardStep {
    handle(request) {
        console.log("Step 3: Confirm data");
         
        //...confirmation logic goes here

        request.dataConfirmed = true;
        super.process(request);
    }
}

class OutcomeStep extends WizardStep {
    handle(request) {
        if (!request.dataConfirmed) {
            console.log("Error: Data must be confirmed before sending outcome.");
            return;
        }
        console.log("Step 4: Outcome");
        request.outcomeSent = true;
        super.process(request);
    }
}


// Setting up the chain
const step1 = new PersonalDataStep();
const step2 = new UploadPhotoStep();
const step3 = new ConfirmDataStep();
const step4 = new OutcomeStep();

step1.setNext(step2).setNext(step3).setNext(step4);

// Running the wizard
console.log("Starting the wizard...");
const request = {};
step1.handle(request);
console.log("Wizard completed.");

Spiegazione

  1. Ogni step estende la classe base WizardStep e implementa il proprio metodo handle.

  2. Il metodo setNext() viene utilizzato per creare dinamicamente e chiaramente la catena.

  3. La catena garantisce che gli step vengano eseguiti nell'ordine corretto, impedendo l'esecuzione di uno step se quello precedente non è stato completato.


Benefici dell'Uso del Pattern Chain of Responsibility

  • Disaccoppia i mittenti delle richieste dai destinatari, rendendo il codice più modulare.

  • Logica di gestione flessibile, consentendo di modificare gli step dinamicamente.

  • Maggiore manutenibilità, grazie all'isolamento della logica in classi separate.

Applicando il modello Chain of Responsibility, garantiamo che ogni passaggio della procedura guidata venga eseguito nell'ordine corretto, mantenendo al contempo una logica pulita e riutilizzabile.



Follow me #techelopment

Official site: www.techelopment.it
facebook: Techelopment
instagram: @techelopment
X: techelopment
Bluesky: @techelopment
telegram: @techelopment_channel
whatsapp: Techelopment
youtube: @techelopment