lunedì 27 febbraio 2012

Il pattern chain of responsability, un esempio concreto

Oggi parliamo del pattern Chain of responsability, uno dei pattern comportamentali del catalogo GOF.
Il concetto alla base di questo pattern è da un lato semplice e dall’altro molto potente: creare una catena di oggetti correlati tra loro capaci di prendere in carico e gestire delle richieste.

Ogni oggetto della catena:

  • può esaminare la richiesta in arrivo,
  • può eventualmente elaborare la richiesta
  • può eventualmente inoltrare la richiesta al suo successore

Sta a noi decidere se la richiesta debba arrivare alla fine della catena e quindi attraversarla completamente oppure fermarsi a metà strada perchè abbiamo completato le operazioni da fare per quella particolare richiesta.

L’implementazione generale del pattern è abbastanza semplice, occorrono:
  • Una classe astratta Handler, che sarebbe un singolo anello della nostra catena, con un metodo astratto “handle(HandleRequest request)” e una variabile di classe “nextHandle” che rappresenta il successivo oggetto della catena di responsabilità.
  • Le classi concrete HandlerUno, HandlerDue e HandlerTre che implementano il metodo handle ed eventualmente invocano nextHandle.handle(request). Ovviamente in questo caso specifico, HandlerUno può elaborare e passare a HandlerDue che può elaborare e passare a HandlerTre che termina la catena.

Non mi sono mai trovato nella situazione di dover implementare da zero questo pattern ma in effetti l’ho usato decine di volte all’interno delle mie Web Application dato che Java ci offre gratuitamente un’ottima implementazione (forse anche qualcosa in più) quando andiamo ad utilizzare le catene di Filtri su una Servlet.

La FilterChain del J2EE viene gestita con l’interfaccia Filter che espone il metodo doFilter(). Rispetto a quanto descritto prima:
  • Filter è l’equivalente della mia classe Handler
  • doFilter() è l’equivalente del mio metodo handler()

Non abbiamo un riferimento esplicito al successore della catena ed ecco perchè Filter è un’interfaccia e non una classe astratta. La catena viene mappata semplicemente nel web.xml mettendo in fila, uno sotto l’altro, i filtri da eseguire prima di arrivare alla Servlet, che rappresenta la terminazione della catena stessa.
Sarà il ServletContainer che gestirà per noi in maniera trasparente la FilterChain.

Se fin qui è chiaro, veniamo all’uso pratico.
Ho una servlet che riceve in ingresso una request con un parametro xml abbastanza corposo e strutturato, la mia servlet assume che se il tracciato xml non ha particolari problemi, questo possa essere inoltrato verso un componente JMS che si preoccuperà di fare dell’altro.

Quali sono i “particolari problemi” del mio xml?
  • Filtro1: Verifico che il tracciato xml sia sintatticamente corretto: tag aperti e chiusi correttamente e solo tag attesi.
  • Filtro2: Verifico che la semantica dei miei tag sia quella attesa ovvero tag obbligatori, tag con campi a lunghezza fissa e limitata, …
  • A questo punto posso gestire logiche specifiche legate al singolo tag, ad esempio:
    • Filtro3: Un campo data che indica la scadenza della mia richiesta mi porta a controllare di trovarmi ancora in un intervallo temporale valido altrimenti posso pure evitare di andare oltre.
    • Filtro4: Un campo che definisce l’attivazione o meno di un certo servizio potrebbe scatenare un controllo sul database e solo in caso di “servizio trovato” posso mandare avanti la mia richiesta.
    • Filtro5: Un particolare campo di “lookup” potrebbe scatenare un controllo remoto verso un’applicazione esterna e solo se il controllo è ok posso continuare a processare la mia richiesta.

Se la mia richiesta supera l’intera catena di controlli e operazioni dei 5 filtri descritti allora arriva finalmente alla Servlet e completa il suo ciclo di vita, altrimenti si blocca a metà strada.

Approfondimento >> Lo scopo di questo articolo non è quello di descrivere il mapping e l’utilizzo dei filtri, per chi tuttavia fosse interessato ai dettagli, ecco un link abbastanza autorevole http://www.oracle.com/technetwork/java/filters-137243.html

La potenza della catena di responsabilità sta nel fatto che sono sempre in grado di aggiungere altri “anelli” alla mia catena nel momento in cui si presentano nuove esigenze particolari e tutto senza modificare le classi esistenti ma soltanto creando le nuove classi Handler.

L’esempio di cui abbiamo parlato riguarda una richiesta che deve essere elaborata a più mani dai diversi filtri, quindi in effetti so già che questa verrà processata ragionevolmente da tutti i componenti della mia catena, almeno in uno scenario medio.
Il pattern chain of responsability in effetti serve anche quando non sappiamo esattamente quale tra diverse classi sia in grado di  gestire una data richiesta e quindi lasciamo il compito agli stessi elementi della catena di capirlo e in maniera “responsabile” di passarsi la richiesta tra loro fino a trovare l’handler più adatto.
Giusto per fare un altro esempio rapido, potrei avere una serie di classi nella chain of responsability che elaborano un file passato come parametro (quindi la mia request).
A seconda che il file sia un documento di testo, un’immagine o un filmato dovrò compiere operazioni differenti e terminare il processo senza passare ad altri la mia richiesta. Se nel tempo dovessi trovarmi a gestire nuove tipologie di file mi basterà implementare le classi concrete adatte allo scopo.

Nessun commento:

Posta un commento