MokaByte Numero 24  -  Novembre 98
 
I Pattern sono biscotti?
di 
Andrea Trentini 
Cosa saranno mai 'sti Pattern? Si puó dire che i pattern sono semplicemente descrizioni di problemi e della loro  soluzione

 


 
 


Continua la serie sulla progettazione con UML. Stavolta parliamo dei Design Pattern: un modo di vedere il software in maniera quasi-astratta, parlando per modelli, cercando di generalizzare e far emergere meccanismi spesso usati ripetutamente senza rendersene conto (magari ripensandoli ogni volta da capo!).


Pattern alla crema

Si mangiano? Cosa saranno mai 'sti Pattern?
Parafrasando dal libro (vedere bibliografia) si puó dire che i pattern sono semplicemente "descrizioni di problemi e della loro (possibile, ma non unica) soluzione".
Quando affrontate un problema software cosa dovete produrre? Dovete disegnare un modello (un sistema) che rappresenti in una certa misura l'idea che ha in testa il cliente (se siete fan di Guglielmo Cancelli, costruite qualcosa, non importa cosa, e poi fate il lavaggio del cervello al cliente ;-). Il mito del "riuso del software" vorrebbe promettervi che man mano che andate avanti negli anni vi potete costruire una libreria di SW riutilizzabile per scopi futuri. In realtá non é infrequente che le uniche cose che ci si porta dietro tra un progetto e l'altro siano le proprie idee!
I pattern sono proprio questo! IDEE, meccanismi, strutture ad alto livello, non (necessariamente) legate a qualche linguaggio di programmazione. Descrivono un problema, una soluzione e le possibili conseguenze della soluzione adottata (ho messo i termini usate nel libro in neretto). Nel libro sono catalogati per tipologia e ogni pattern é documentato stile man di Unix, io faró un racconto meno sistematico e legando tutto il discorso all'uso in Java.

 Se scorrete il libro trovate:

Scrivere un pattern vuol dire soltanto riordinare e formalizzare una propria idea in modo che sia facilmente riutilizzabile (perché é stata generalizzata) e facilmente associabile ad un nuovo problema.
Conoscere i pattern vuol dire sapere che esistono delle analogie tra modelli e che tali analogie si possono sfruttare per non dover ripensare da capo tutto ogni volta. É un po' lo stesso discorso (piú ad alto livello) degli algoritmi: quando dovete scrivere un SORT non vi mettete a pensare (vero!?), ma allungate una mano verso il Knuth... Idem con i pattern, mentre progettate un sistema vi accorgete che dovete realizzare ad esempio un sistema di notifica dei cambiamenti, non vi mettete a pensare, ma allungate una mano verso il GOF (come é informalmente chiamato il libro) e cercate un pattern utile (guarda caso esiste: Observer/Observable).
Vediamone qualcuno, fra i piú semplici, tanto per vedere se é un argomento interessante come affermo ;-)

Observer/Observable

Vi sono familiari queste due parole? Se avete usato a fondo Java avrete incontrato queste due classi (in realtá una é una interfaccia).
Quando vi serve un meccanismo per cui un oggetto controlla (nel senso di monitoraggio) lo stato di un altro (esempio classico: un widget di interfaccia utente che deve aggiornarsi quando l'oggetto che rappresenta cambia stato), ma il controllato deve sapere il meno possibile del controllante, cosa vi serve?

Una coppia Observer/Observable! Questo pattern si chiama anche Publisher/Subscriber... provate a capire perché... ;-)

La situazione é questa, facciamo uno scenario ipotetico:

Voglio troppo? No, e poi usando questo pattern é veramente semplice da realizzare...
Diciamo che la tabella dei dati da visualizzare venga chiamata OSSERVABILE (per ora non sappiamo cosa significa), lei sa solo che se deve notificare un proprio cambiamento di stato puó mandare un messaggio del tipo: "ehila! ho cambiato stato!".
Diciamo anche che ogni widget per rappresentare i dati venga chiamato OSSERVATORE, lui sa solo che puó ricevere messaggi del tipo di cui sopra e che saprá chi é il mittente del messaggio.
Se collego opportunamente le istanze degli oggetti tabella (Observable) con alcune istanze degli oggetti widget (Observer), a runtime questi si scambieranno messaggi del tipo: "guarda che ho cambiato stato!" e "ah giá! aspetta che aggiorno la visualizzazione!".

Formalmente:

Nel caso dei widget di visualizzazione dati tabellari, proviamo a buttar giú qualche idea: Da qualche parte nel codice basta fare:
 
TableData t = new TableData(...);

...

BarGraphView b= new BarGraphView(...);

IstogramView i=new IstogramView(...);

GraphView g=new GraphView(...);
 
 

Panel p=new Panel(...);

... // aggiungo i Component (le viste) al Panel
 
 

// collego gli Observer all'Observable

t.subscribe(b);

t.subscribe(i);

t.subscribe(g);

...
 
 

// non appena t lancia un notify... b, i e g se ne accorgono...

Singleton e Singleton "esteso"

J. B. Singleton, un famoso scrittore di libri gialli... no, forse mi sbaglio... Stavo scherzando, quello che si chiama "Singleton" é uno dei pattern piú semplici che si possono incontrare. É facile da spiegare e da comprendere (sempre nell'ottica di "entrare nel giro") e consta, nella versione "base", di una sola classe.
Quando vi serve una classe che generi una sola istanza (per singola JVM, ovviamente) cosa fate? Bé... usate un Singleton. Provate a dargli un'occhio:

Le sue caratteristiche:

Il succo del funzionamento del Singleton é tutto nell'implementazione del metodo getInstance(), tale metodo infatti ritorna SEMPRE un'istanza (l'unica) di Singleton, ma la crea solo LA PRIMA VOLTA CHE VIENE CHIAMATO.
Una prima estensione del Singleton si ha nel caso in cui si voglia controllare anche l'accesso all'istanza (per esempio tramite password), basta variare un po' sul tema: pensate per esempio ad un metodo getInstance(String password) che prima di effettuare il return controlla se la password passata é valida...
L'estensione piú articolata, che io chiamo "Singleton esteso", non la trovate sul libro dei Pattern, ma la ritengo interessante, soprattutto quando si ha a che fare con i database. Eccola:

Come funziona? Il meccanismo é analogo al precedente, solo espanso per gestire una collezione di istanze, attraverso una Hashtable. il getInstance() stavolta vuole una chiave di accesso e restituisce l'istanza associata a quella chiave (istanziando se necessario).
Perché ho detto che si incontra spesso usando i database? Se le istanze degli oggetti (di solito ALCUNI oggetti, i piú significativi, non tutti) sono sincronizzate con un database (cioé lo stato dell'oggetto é memorizzato in una o piú tabelle del db e ogni modifica sull'oggetto si ripercuote sul db) é ovvio che sará meglio NON AVERE PIÚ ISTANZE DI QUEGLI OGGETTI in giro. Facciamo un esempio pratico: un sistema per modellare una azienda avrá quasi sicuramente una classe Dipendente, se ho DUE istanze di Dipendente("Mario Rossi - matricola 777") e su una modifico il livello aziendale mentre sull'altra modifico l'indirizzo, che succede? Nella peggiore delle ipotesi l'ultimo (oggetto) che si aggiorna nel DB fa fede, l'altro "ciccia". Nella migliore vengono modificati solo i valori cambiati, ma poi restano degli oggetti in giro NON AGGIORNATI COL DB.
A maggior ragione se ho dei metodi sincronizzati nella classe che sto trattando. I lock sugli oggetti sono per singola istanza... ;-)

Avere o Essere ?

Se vedete uno schema del genere e vi dicono di implementarlo in Java, cosa rispondete?

Ereditarietá multipla? Ma se in Java non c'é! Cosa vai dicendo!
In effetti non é possibile implementare direttamente (nel senso di una mappatura 1 a 1) in Java lo schema precedente, ma ci terrei a mostrarvi la soluzione (banalissima quando l'avrete vista!) perché mi serve da spunto per un certo discorso... e per capire perché questo paragrafo si intitola cosí!
Lo schema di soluzione é questo:

Detto a parole:

Cosí facendo avremo una classe (Figlio) che al mondo esterno apparirá come "figlia" di DUE classi contemporaneamente (Padre1 e Padre2). L'interfaccia di Figlio infatti sará l'unione delle due interfacce e gli utilizzatori della classe Figlio vedranno accettati (e serviti) tutti i messaggi (o chiamate di metodi se preferite) compatibili con Padre1 e Padre2. Avremo una classe "semanticamente" figlia di altre due, mentre "sintatticamente" solo di una.
Gli "ottimizzatori" e i puristi ora salteranno su a dire: bé, ma é una soluzione lenta, si aggiunge uno step all'esecuzione di ogni metodo di Padre2! Io risponderó: certo che sí, ma in generale anche il normale meccanismo di binding di un linguaggio a oggetti (non tanto dissimile da questo!) non é che sia proprio velocissimo... in ogni caso provate a vedere se vi convince il seguente commento...
Quale delle due classi taglio? Intendo dire: tra le due che dovrei "estendere", quale "implemento"? Direi che ci sono almeno TRE "approcci di taglio" che si possono adottare:
    1. SEMANTICO: fra le due classi padre, qual'é quella che é "piú padre" dell'altra? Ad esempio: un automobile con le ali é piú AUTO o piú AEREO? (dovete scegliere voi, dipende anche dal contesto applicativo). Ovviamente taglio quella "MENO padre"...
    2. PRESTAZIONALE: visto che il meccanismo aggiunge lentezza, quale delle due classi padre viene "chiamata" piú spesso? Taglio quella meno chiamata...
    3.PRAGMATICO: quale delle due classi ha meno metodi? Voglio scrivere di meno (!!!) e taglio quella che ha meno metodi...
Dei tre io preferisco il PRIMO (é il piú coerente con la progettazione a oggetti), ma ovviamente ogni sviluppatore é libero di scegliere la strada che gli é piú congeniale.
Il perché del titolo di questo paragrafo si evince (!!!) giá guardando i due schemi: siamo passati da una situazione in cui Figlio isA Padre2 ad una in cui Figlio hasA Padre2 (e isA Padre2_Interface), cioé ad una situazione in cui possedere un altro oggetto permette di "diventarlo". Naturalmente non mi interessa fare discorsi sul cosa é meglio (l'Avere o l'Essere), in questo ambito si discute solo sul "cosa si puó poi realizzare effettivamente".
L'ultima frase del paragrafo precedente mi permette di agganciare ancora un commento: quando si progetta con UML (come con altri formalismi similari), non é detto che ció che si disegna abbia una implementazione straightforward, immediata, al volo, 1 a 1, etc. Molto spesso il progetto ad alto livello descrive una situazione astratta (dall'implementazione), é un disegno che cattura la struttura e la funzione, ma senza entrare nei dettagli (implementativi appunto) dovuti al particolare linguaggio utilizzato per realizzarlo. Detto questo, c'é da domandarsi se, progettando, si devono produrre schemi ad alto o a basso livello (piú o meno vicini a quella che sará l'implementazione effettiva), la mia risposta é: dipende dal contesto, a volte é necessario "stare bassi" (vicini all'implementazione), a volte "stare alti" (macro schema, overview, etc.) e a volte puó valer la pena produrre entrambi (come se fossero vari livelli di "zoom").

Bibliografia

É pressoché la stessa dell'articolo precedente (UML e OOP), l'unica ripetizione che valga la pena fare é:

Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (Gang Of Four) Addison Wesley 1994

Vorrei citarvi solo una frase dalla prefazione: "Una parola di incoraggiamento: non preoccupatevi se non capite completamente questo libro alla prima lettura. Non lo abbiamo capito nemmeno noi alla prima scrittura!"

L'ho riportata solo per esemplificare l'atteggiamento degli autori: gioviali, simpatici e non "sul piedistallo". Il loro intento é quello di raccogliere le loro esperienze sul riuso del software e trasmetterle. Si rendono conto che le astrazioni spinte sono a volte difficili da comprendere (soprattutto per programmatori molto legati ad un linguaggio ;-), ma cercano di mettervi a vostro agio con molte esemplificazioni e con un linguaggio chiaro (e con battute di spirito!). Un po' il contrario dell'atteggiamento "se non capisci sei stupido" di molti informatici d'elite che parlano per acronimi. ... e, come al solito, cercate su Internet.
 
 
 
 
 
 


MokaByte Web  1998 - www.mokabyte.it 
MokaByte ricerca nuovi collaboratori. 
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it