Introduzione
Chi ha fatto
latino alle superiori, sa che memento significa ricordare; in effetti il
compito di questo pattern è proprio quello di tenere memoria delle
varie azioni che vengono svolte da un determinato oggetto.
Si pensi ad
un'applicazione che offre interazione con l'utente (basta prendere un normalissimo
text editor, word processor, o un programma di grafica); in questi programmi,
o comunque in quelli seri, viene sempre offerta la possibilità di
tornare sui propri passi disfacendo (undo) l'azione appena fatta. Questa
possibilità può essere più o meno utile, basandosi
sulla granularità delle azioni considerate; senz'altro risulterebbe
molto più utile avere a disposizione un meccanismo di undo multilivello,
cioè la possibilità di poter annullare più azioni
(anche contemporaneamente).
Il programma
che mette a disposizione un tale meccanismo si deve mantenere memorizzata
una sorta di history dei vari stati del documento, in modo da poter riportare
il tutto ad una situazione precedente; ovviamente è comodissimo
avere anche la possibilità di disfare quello che abbiamo disfatto,
o molto più semplicemente di avere un meccanismo di redo (anche
questo presente nei programmi che mettono a disposizione il meccanismo
di undo).
Altre volte
è necessario semplicemente tenere memorizzati dei checkpoint degli
stati di certi oggetti, anche in questo caso per implementare dei meccanismi
di roll back (si veda ad esempio le transazioni atomiche nei DBMS).
Si deve però
trovare un metodo efficiente e non dispendioso in fatto di memoria e risorse
di tenere memorizzata questa history; senz'altro non si può pensare
di tenere in memoria (anche secondaria) una copia del documento per ogni
azione eseguita. Vedremo come il pattern memento risolve in modo pratica
ed elegante (caratteristica ormai appurata dei pattern) questo tipo di
problema.
Il problema
Il problema principale
consiste dunque nel memorizzare lo stato di un certo oggetto, senza però
violare l'incapsulazione: di solito solo l'oggetto stesso può accedere
direttamente ai propri dati; e quindi un oggetto esterno non è in
grado di accedere allo stato dell'oggetto interessato, per salvarlo.
L'idea di base
della soluzione è quella di utilizzare un altro oggetto (che chiameremo
appunto memento), il cui scopo principale è quello di memorizzare
lo stato di un altro oggetto (che chiameremo creatore del memento).
L'oggetto che
si occuperà del meccanismo di undo dovrà richiedere al creatore
(che ricordiamo è soprattutto l'oggetto di cui vogliamo tenere memorizzati
i vari stati), un oggetto memento, al momento del checkpoint; il creatore
creerà a quel punto un oggetto memento inzializzandolo con le informazioni
necessarie (e sufficienti) a ripristinare tale stato. Al momento che il
meccanismo di undo entrerà in azione dovrà semplicemente
passare il memento al creatore, che lo utilizzerà per riportare
se stesso allo stato in cui era stato creato il memento.
Notare che a
soluzione del memento disaccoppia il meccanismo di undo dai vari oggetti
che dovranno supportare tale meccanismo: il memento non lascia trasparire
lo stato dell'oggetto memorizzato ed il meccanismo di undo non ha accesso
allo stato dell'oggetto osservato.
Il pattern
Formalizziamo
adesso i partecipanti al pattern (che senz'altro si contraddistingue per
la sua semplicità), mantenendo i nomi inglesi (onde evitare traduzioni
un pò abbozzate):
-
Memento: è
l'oggetto che si occupa di tenere memorizzato lo stato di un oggetto (l'Originator);
tale oggetto nasconde lo stato dell'oggetto agli oggetti di altre classi;
gli altri oggetti hanno accesso solo ad un'interfaccia povera del memento:
in effetti gli altri oggetti lo possono solo passare all'oggetto che deve
ripristinare il suo stato. Ovviamente, invece, l'originator dovrà
avere accesso a tutti i dati memorizzati nel memento che gli serviranno
per ripristinare il proprio stato.
-
Originator: è
l'oggetto di cui si vuole tenere memorizzato lo stato, ed è quello
che si occupa di creare i vari memento, coi dati caratteristici del proprio
stato in quel momento; inoltre utilizzerà anche l'oggetto memento
per ritornare allo stato memorizzato nel memento.
-
Caretaker: è
l'oggetto che implementa il meccanismo di undo; si occupa di richiedere
un oggetto memento all'originator, a tenere memorizzati i vari memento
e di passarli all'originator, al momento che è necessario applicare
il meccanismo di undo.
Tipicamente un oggetto
memento sarà caratterizzato da due metodi: GetState e SetState,
il cui significato dovrebbe essere abbastanza chiaro: l'Originator chiamerà
SetState al momento della creazione del memento per inizializzarlo con
tutti i dati necessari; quando poi riceverà un memento (in tal caso
è stato messo in esecuzione il meccanismo di undo), chiamerà
GetState per ottenere le informazioni sullo stato che aveva precedentemente
memorizzato nel memento.
Una sorta di
interazione fra i vari oggetti può essere riassunta nel seguente
diagramma di interazione:
Caretaker |
|
Originator |
|
Memento |
CreateMemento() |
----> |
new Memento() |
----> |
|
|
|
SetState() |
----> |
|
... |
... |
... |
... |
|
SetMemento(m) |
----> |
GetState() |
----> |
|
Come sempre vi
lascio adesso ad Andrea Trentini (vedi)che
vi mostrerà come in pratica può essere implementato e soprattutto
utilizzato un memento. |