I testi sui design pattern sono ormai legione, dal
classico della Gang of Four (GoF) all'apprezzato
testo di Fowler sulle applicazioni di classe enterprise, al
Metsker, al
Larman ed al
Nilsson, ai testi in PDF come il
Trowbridge o il
Cooper, senza dimenticare le
risorse on line comprensive di
introduzioni in italiano ed
in lingua, ed i
gruppi organizzati; né potevano mancare le risorse sull'argomento per i paradigmi più
alla moda come
SOA ed
AJAX.
Molti dei tradizionali pattern
GoF sono inoltre
già disponibili nei principali framework di programmazione. Questo ennesimo libro sull'argomento adotta un approccio diverso da quello tradizionale, reso celebre proprio dal testo primigenio della
GoF, di presentare un catalogo tassonomico dei pattern esistenti, ritenuto poco adatto ai principianti. La strada percorsa è invece quella di illustrare la creazione di due applicazioni di esempio, un visualizzatore di file multiformato ed un Sudoku, presentando l'uso dei pattern per risolvere un problema e solo successivamente discutendo la loro utilità in termini generali.
Lo stesso dicasi per l'uso di UML 2.0 nella progettazione, anche se in questo caso l'autore preferisce introdurre brevemente nel
primo capitolo i diagrammi principali (
class,
sequence e
package) ed illustrare i rimanenti in un'apposita Appendice. Il
secondo capitolo salta a piè pari ogni introduzione teorica, a parte due pagine sulla terminologia, per mostrare subito la realizzazione del visualizzatore di file tramite tre pattern, due
creazionali ed uno comportamentale. La scelta di progetto di permettere al visualizzatore di aprire un solo file alla volta porta naturalmente all'implementazione di un pattern
Singleton. Su questo celebre pattern la discussione è piuttosto approfondita, mostrando anche le implicazioni dell'aggiunta di funzionalità di passaggio dei parametri e gestione delle eccezioni alla classe di implementazione, mostrando così come, all'atto pratico, la forma base del
Singleton è anche quella più semplice ed elegante.
La creazione di un visualizzatore di file, che consenta una semplice estensione mediante una struttura a plug-in del codice per poter visualizzare nuovi tipi di file, richiede l'uso del pattern
Dynamic Linkage, la cui caratteristica principale è il disaccoppiamento completo della classe chiamante da quella chiamata. Com'è facile immaginare, questo pattern fa uso della
reflection, e l'unico
collo di bottiglia in termini di
manutenibilità diventa la scelta di un'adeguata struttura di corrispondenza fra tipo di file e visualizzatore da utilizzare. L'implementazione dei singoli visualizzatori passa per quella di una classe astratta, che definisce lo
scheletro del sottosistema di visualizzazione, lasciando l'implementazione dei dettagli alle sottoclassi di implementazione concrete.
Viene utilizzato all'uopo un pattern comportamentale di uso assai comune,
Template Method, che oltre a favorire il riutilizzo del codice, permette un adeguato impiego delle risorse di progetto: solitamente infatti architetti e sviluppatori esperti definiscono il
Template Method, lasciando alle figure junior i dettagli di più basso livello. Spesso peraltro l'implementazione delle classi concrete è, come in questo caso,
parallelizzabile. Per usare una frase fatta, questo secondo capitolo varrebbe da solo il costo di copertina. La maggiore difficoltà nell'uso dei design pattern è che li si può anche mandare a memoria, ma ci vuole poi una certa esperienza di sviluppo per capire se e quando applicarli: il capitolo affronta la questione facendo
nascere i pattern utilizzati dalle esigenze dell'applicazione. L'implementazione stessa presenta molti punti di possibile miglioramento, chiaramente evidenziati dall'autore, ma la presentazione delle fasi di progettazione del codice mi ha lasciato davvero soddisfatto.
Nel
terzo capitolo viene illustrata l'applicazione di esempio per il gioco del Sudoku, che ci accompagnerà fino alla fine del testo, e l'architettura dei package che la compongono. La definizione dell'interfaccia utente passa, manco a dirlo, per l'implementazione di un pattern
Model-View-Controller (MVC), sviluppato
from scratch senza fare uso di uno dei numerosi framework disponibili. Il capitolo è mediamente lungo, ma concettualmente non difficile da seguire; buona parte delle pagine è occupata dai lunghi listati. Per gli eventi della UI viene illustrato l'uso del pattern
Observer (noto anche come
publish-subscribe), implementato in maniera nativa dai gestori di eventi di molti linguaggi moderni. Il pattern stabilisce una relazione uno-a-molti fra oggetti in modo che, quando uno di essi cambia stato, tutti gli oggetti dipendenti ne vengano notificati; in questo caso più che negli altri, non avrebbe guastato il
classico diagramma UML per l'illustrazione del pattern. Viene discusso infine un altro pattern assai comune nell'implementazione delle interfacce utente, caratterizzate a volte dalla ripetizione di alcune tediose funzionalità. Il pattern
Delegate rappresenta una via alternativa, ed in questo caso più elegante, all'ereditarietà per il riutilizzo del codice: i metodi di una nuova classe non fanno altre che richiamarne altri esistenti nella classe riutilizzata.
Il
quarto capitolo si occupa della gestione dello stato dell'applicazione. Questa rischia facilmente di diventare un incubo in termini di
manutenibilità del codice nelle applicazioni procedurali, ove solitamente ad ogni stato è associata una costante e le transizioni fra stati vengono gestite con poco eleganti
if o
switch. L'uso del pattern
State consente in tal caso, non solo di semplificare il codice, ma anche di rendere esplicite le transizioni di stato, poiché il codice relativo è presente nelle classi stesse che implementano i singoli stati. Per la verità, la creazione (e la condivisione) degli stati passa per un altro celebre pattern
creazionale, l'
Abstract Factory, che vedremo
in azione nel capitolo successivo ed in quello finale, anche se viene accennato alla fine di questo. Ancora, si preferisce non creare un nuovo stato — inteso come oggetto ad esso associato — se esso è già presente in memoria, ma piuttosto riutilizzarlo: un altro pattern, noto come
Flyweight. L'autore discute, infine, un'implementazione alternativa del pattern
State basata sull'uso di una tabella di gestione degli stati; l'approccio offre una maggiore configurabilità all'utente finale, al prezzo del maggior costo di risorse legato all'accesso alla tabella.
Nel
quinto capitolo, l'introduzione delle funzionalità di
undo/redo dei comandi del gioco portano all'introduzione di un potente pattern comportamentale,
Command. In questo pattern, ciascuna tipologia di azione è rappresentata da una specifica classe; ogni classe conserva il valore precedente del comando in un campo privato, sì da facilitare le operazioni di
undo/redo. Il pattern
Composite, che permette la composizione degli oggetti in strutture ad albero in modo che le classi client possano trattare gli oggetti individuali e le composizioni di oggetti allo stesso modo, viene invece utilizzato per estendere l'
undo/redo alle macro, ovvero a insiemi di comandi. Per svincolare l'algoritmo di creazione di un nuovo schema di gioco dal resto dell'applicazione viene usato il pattern
Strategy, che permette di incapsulare una famiglia di algoritmi, rendendoli intercambiabili: al solito, questo facilita l'estensione dell'applicazione con nuovi schemi di gioco e riduce la necessità di codice condizionale.
Il
sesto capitolo è una sorta di
interludio in cui l'implementazione di una funzionalità minore, la verifica via Web se si possiede la versione più aggiornata del programma, diventa il pretesto per presentare due pattern assai utili per affrontare
casi particolari di una certa classe,
Special Case e
Null Object. Quest'ultimo è un caso particolare di
Special Case, che fornisce un surrogato per rappresentare la mancanza di un oggetto di un certo tipo: l'oggetto dà un'implementazione che
non fa nulla, con metodi vuoti dell'interfaccia esposta dalla classe principale.
Il
settimo capitolo affronta una serie di pattern di peculiare importanza, quelli per l'accesso ai dati, per l'implementazione del codice di salvataggio e recupero delle partite dell'applicazione di esempio. Tre sono i pattern utilizzati:
Data Access Object, che permette di astrarre ed incapsulare tutti gli accessi ad una fonte dati (nello specifico, si disaccoppiano le entità di business dalla modalità di persistenza);
Memento, che senza violare l'incapsulamento permette di esternalizzare lo stato interno di un oggetto (spesso al fine di recuperarlo in seguito), utilizzato in modo implicito da molti linguaggi moderni per la serializzazione degli oggetti; e
Façade, un pattern di uso massivo nella piattaforma .NET, utilizzato per esporre un'unica interfaccia verso sottosistemi complessi. In questo capitolo e nell'ultimo viene fatto un uso maggiore dei diagrammi UML per illustrare il funzionamento dei pattern. La semplicità dei requisiti di progetto — parliamo di una semplice serializzazione/deserializzazione su file di testo di strutture dati non troppo complesse — non permette comunque di apprezzare appieno l'utilità di questi pattern, che si dispiega appieno in realizzazioni architetturali assai più complesse (e potenti) quali il
Data Access Application Block (DAAB).
L'
ottavo capitolo, dedicato in massima parte ai pattern creazionali (ma anche a qualcuno strutturale), è particolarmente lungo: da solo, copre più di un quinto del libro. L'intero capitolo è devoto ad un unico obiettivo: migliorare le prestazioni, diminuire la complessità, aumentare il riutilizzo del codice nella fase di creazione degli oggetti. Viene presentata una lunga serie di pattern:
Lazy Load;
Cache Management;
Object Pool;
Prototype;
Abstract Factory, un pattern direttamente legato alla
Dependency Injection;
Builder. Ad ognuno di questi è dedicato un paragrafo a sé, svincolato dallo sviluppo dell'applicazione di esempio che ci ha accompagnati per tutto il libro, anche se sporadicamente vi si fa ritorno per qualche esempio di codice. Il risultato non è comunque privo di organicità ed in particolare risulta molto utile il diagramma di flusso a pagina 196, che fornisce un criterio di scelta su quale dei pattern presentati utilizzare. Il testo non esaurisce di certo l'ampia casistica dei pattern, riportando però in Appendice B un elenco più completo.