L’esempio proposto è tratto da un prodotto in corso di realizzazione in ambito universitario. Il prodotto non usa un database per la memorizzazione delle informazioni, ma si basa sull’uso estensivo della persistenza degli oggetti attraverso il pattern Memento.

La nostra esigenza principale è non vincolare l’utilizzo a un database e realizzare un prodotto 100% pure Java. Anche se esistono database pure Java, usandone uno bisognerebbe cominciare a scrivere SQL, mentre non abbiamo bisogno di query complicate: dobbiamo semplicemente salvare lo stato di alcuni oggetti importanti.

Il sorgente completo dell’esempio è disponibile insieme all’articolo.

Come abbiamo usato il Memento

Lo schema delle classi mostra i tre oggetti principali, per ora definiti come interfacce:

Figura 1 - Schema delle classi usate per applicare il pattern Memento
Figura 1 - Schema delle classi usate per applicare il pattern Memento
  • PersistentI: ogni oggetto che voglia essere salvato deve implementare questa interfaccia; non gli interessa come e dove verranno salvati gli stati che lo rappresentano.
  • StateI: gli oggetti usati come Memento, che incapsulano l’informazione estratta dal PersistentI; non interessa loro quale oggetto rappresentino, né dove e come vengano salvati.
  • StorageI: le classi capaci di gestire la memorizzazione, normalmente su disco; non interessa loro da dove arrivino gli stati da salvare.

Il meccanismo è semplice: un oggetto persistente sa generare un oggetto, il Memento, che racchiude l’informazione significativa del proprio stato corrente e sa assorbire un Memento per reinizializzarsi.

Per salvare lo stato di un oggetto persistente (PersistentI) gli si chiede di generare un Memento (StateI) chiamando spawnState(); per reinizializzarlo gli si fornisce un Memento chiamando absorbState(StateI). Lo StorageI, normalmente chiamato caretaker, gestisce la memorizzazione degli stati degli oggetti.

Un’ipotesi di funzionamento

Supponendo di avere già un’implementazione dei tre ruoli — oggetto da salvare, stato e caretaker — potremmo eseguire un brano di programma come questo:

PersistentI p;
StorageI s;
// creazione degli oggetti
s.saveState(p.spawnState());
// una chiamata che cambia lo stato di p
p.metodo();
// ritorno allo stato precedente di p
p.absorbState(s.retrieveState("ID dell'oggetto"));

Considerate le interfacce come se fossero classi implementate: nell’esempio le implementazioni esistono. TestPersistent realizza un generatore di stati ed è anche stato di se stesso; SerialStorage realizza il caretaker.

Come usare l’esempio

Dopo aver compilato il sorgente, conviene generare e consultare anche la documentazione. Create una directory vuota chiamata storage nella directory corrente, dove si trovano i file .class: sarà la directory in cui verranno salvate le immagini degli oggetti.

Dal prompt lanciate:

java Main > out.html

Otterrete un output HTML che mostra come, dopo aver cambiato lo stato dell’oggetto, sia possibile tornare al vecchio valore recuperando dallo storage lo stato salvato in precedenza.

Perché usare un oggetto “stato di se stesso”?

La domanda è in realtà mal posta: bisognerebbe chiedersi perché estrarre lo stato, passarlo al caretaker e poi salvarlo se si usa l’oggetto stesso come stato.

Limitandosi a questa situazione avrebbe poco senso. Il meccanismo permette però di introdurre nel sistema oggetti che non sono stato di se stessi e di farli trattare dagli stessi caretaker. In Main.java vengono infatti usati due tipi di oggetti persistenti e un solo storage. La separazione dei compiti fra i ruoli del pattern Memento permette così di aggiungere funzionalità a posteriori: la classe TestPersistentProp, per esempio, è stata aggiunta dopo le prime prove senza modificare il caretaker.

Alla prossima

…stesso BatSito (hoops!)…