Home > GOF Pattern, java > GoF Patterns: Memento

GoF Patterns: Memento

23 febbraio 2012

Translate in English with Google Translate
In questo articolo tratterò il pattern Memento anche detto Token

Motivazione
Si tratta di un pattern comportamentale basato su oggetti che viene utilizzato quando si ha necessità di ripristinare lo stato di un oggetto ad un suo precedente stato. Ciò richiede di memorizzare gli stati pregressi di un oggetto per poterli eventualmente ripristinare.
Memento, in latino, vuol dire ricordare ed infatti l’obiettivo del pattern è quello di ricordare gli stati precedenti per poterli ripristinare all’occorrenza in un tempo successivo.

I principali text-editor consentono di annullare le modifiche apportate usando il classico CTRL-Z. Lo storico degli stati richiede la memorizzazione di tutte le modifiche apportate e può comportare un eccessivo uso di memoria pertanto occorre assicurarsi di riuscire a gestire le richieste in termini di risorse.
Il Memento si occupa di conservare lo stato di un nostro oggetto e solitamente tutte le modifiche di stato determinano la memorizzazione.
Ma non è sempre necessario memorizzare tutte le modifiche apportate ma solo alcune di esse. Questo migliorerebbe l’uso della memoria utilizzata e consentirebbe di creare uno storico più pulito, contenente ciò di cui abbiamo realmente bisogno. Per esempio quando usiamo uno strumento di Versioning non facciamo il check-in per ogni modifica apportata al file in quanto sarebbe un inutile spreco di spazio, mentre invece lo effettuiamo solo quando riteniamo che sia realmente utile.
Inoltre le modifiche da apportare allo stato potrebbero essere inserite in una coda di modifiche per poi essere salvate solo al termine delle nostre operazioni. Questo ci permetterebbe di scegliere se e quando effettuare il salvataggio dello stato. Per esempio quando usiamo un database ed eseguiamo una serie di istruzioni DML (insert, update, delete) al fine di apportare delle modifiche, solo quando effettueremo il commit tutte le nostre modifiche verranno apportate e cambieranno lo stato. Per esempio se utilizziamo come database Oracle, tutte le modifiche non committate verranno salvate in uno spazio riservato, chiamato Rollback Segment, e solo con l’operazione di commit verrano condivise mentre in alternativa con l’operazione di rollback verranno scartate senza essere incluse negli stati da memorizzare.
Abbiamo detto che il Memento si occupa di detenere lo stato dell’Originator. Quest’ultimo è l’unico in grado di accedere e modificare il Memento, assicurando l’information hiding e svolgendo una funzione di intermediario rispetto al Caretaker un terzo oggetto interessato allo stato dell’Originator.
Il Caretaker ottiene il Memento dall’Originator ma non è in grado di accedere alle informazioni presenti. L’oggetto Memento detenuto dal Caretaker è utilizzato esclusivamente per ripristinare lo stato di interesse.

Partecipanti e Struttura
Questo pattern è composto dai seguenti partecipanti:

  • Caretaker: è interessato allo stato dell’Originator e detiene l’oggetto Memento
  • Originator: memorizza il proprio stato transitorio attraverso l’utilizzo delle proprietà interne mentre memorizza il proprio stato esterno attraverso l’utilizzo del Memento
  • Memento: è l’oggetto che si occupa di detenere lo stato dell’Originator e consente solo a questi di accedervi

Vediamo come si presenta il Pattern Memento utilizzando il Class Diagram in UML:

Conseguenze
Tale pattern presenta i seguenti vantaggi/svantaggi:

  • supporta l’incapsulamento:  consente l’accesso alle informazioni del Memento, solo ad opera dell’Originator che lo ha creato e deve gestirlo. Il Caretaker mantiene una referenza al Memento ma non è in grado di invocare direttamente i metodi set e get.
  • uso eccessivo della memoria: la memorizzazione di tutte le modifiche comporta un eccessivo consumo di memoria per ogni istanza di Memento e questo può determinare problemi di risorse.

Implementazione
Questo pattern si presta a diverse configurazioni, pertanto per evitare di trascurare degli esempi utili, ho pensato a diversi esempi, partendo ovviamente dal più semplice.

  1. ripristino dell’operazione precedente
  2. ripristino di tutte le operazioni precedenti
  3. creazione di un coda di modifiche di stato

Primo esempio: vediamo il caso in cui vogliamo memorizzare lo stato di un oggetto ed avere la possibilità di ripristinare il suo stato precedente. In questo caso lo storico degli stati da memorizzare è relativamente breve, nel senso che possiamo memorizzare solo 2 stati: lo stato attuale e lo stato precedente.

Vediamo come si presenta il pattern in UML in base all’esempio:

L’Originator ed il Memento sono molto simili a dei JavaBean.
L’Originator detiene le informazioni di stato interne nelle sue proprietà e consente di accederne in lettura/scrittura tramite i metodi set e get. Inoltre espone l’accesso al Memento tramite i metodi createMemento() e restoreMemento() che richiamano i metodi set e get del Memento.
Il Memento è visibile solo all’Originator e non ad altre classi. In Java la visibilità dei metodi di una classe è gestita tramite i modificatori public/package/protected/private ma poichè non basterebbe ai nostri scopi, dobbiamo fare ricorso alla creazione di una classe annidata. In particolare creiamo la classe annidata Memento con proprietà/metodi private, in modo tale da garantire l’accesso solo ad opera dell’Originator.
Vediamo come si presenta la classe Originator con la classe annidata Memento:

package pattern.memento;

public class Originator {

    private Object originatorState;

    public void setOriginatorState(Object state) {
        originatorState = state;
    }

    public Object getOriginatorState() {
        return originatorState;
    }

    public Memento createMemento() {
        Memento memento = new Memento();
        memento.setMementoState( originatorState );
        return memento;
    }

    public void restoreMemento(Memento memento) {
        originatorState = memento.getMementoState();
    }

    public class Memento {

        private Object mementoState;

        private Object getMementoState() {
            return this.mementoState;
        }

        private void setMementoState(Object originatorState) {
            this.mementoState = originatorState;
        }
    }
}

A questo punto possiamo creare la classe CareTaker che si interfaccia direttamente con la classe Originator per definire lo stato ed eventualmente ripristinarlo.

package pattern.memento;

import pattern.memento.Originator.Memento;

public class CareTaker {

    public static void main(String[] args) {
        Originator originator = new Originator();

        originator.setOriginatorState("x");
        Memento mementoStato1 = originator.createMemento();
        System.out.println("Stato Originator: " + originator.getOriginatorState() );

        originator.setOriginatorState("y");
        originator.setOriginatorState("z");
        Memento mementoStato2 = originator.createMemento();
        System.out.println("Stato Originator: " + originator.getOriginatorState() );

        originator.restoreMemento(mementoStato1);
        System.out.println("Stato Originator: " + originator.getOriginatorState() );

    }
}

L’esecuzione del codice ci mostra la sequenza delle operazioni di creazione (x), modifica (z) e ripristino (x).

$JAVA_HOME/bin/java pattern.memento.CareTaker
Stato Originator: x
Stato Originator: z
Stato Originator: x

Secondo esempio:  vediamo il caso in cui vogliamo avere la possibilità di ripristinare tutti i suoi stati precedenti attraverso la creazione di una lista di stati.

Vediamo come si presenta il pattern in UML in base all’esempio:

Modifichiamo la classe Caretaker che, invece di avere 2 referenze di Memento, avrà 1 referenza ad una lista di Memento, in particolare ad uno Stack.
La classe CareTakerStack, come nell’esempio precedente, si interfaccia con la classe Originator al fine di creare lo stato iniziale, modificarlo e ripristinarlo.
In questo modo possiamo gestire gli stati attraverso i metodi push() per inserirli, pop() per leggerli.

package pattern.memento;

import java.util.Stack;
import pattern.memento.Originator.Memento;

public class CareTakerStack {

    private static Stack<Memento> mementoStack = new Stack<Memento>();

    public static void main(String[] args) {
        Originator originator = new Originator();

        originator.setOriginatorState("x");
        //memorizzo lo stato attuale
        mementoStack.push( originator.createMemento() );
        System.out.println("Stato Originator: " + originator.getOriginatorState() );

        originator.setOriginatorState("z");
        //memorizzo il nuovo stato attuale
        mementoStack.push( originator.createMemento() );
        System.out.println("Stato Originator: " + originator.getOriginatorState() );

        //rimuovo l'ultimo stato
        mementoStack.pop();

        //ripristino lo stato precedente
        originator.restoreMemento(mementoStack.pop());
        System.out.println("Stato Originator: " + originator.getOriginatorState() );
    }
}

L’esecuzione del codice ci mostra la stessa sequenza di operazioni di creazione, modifica e ripristino realizzate anche nell’esercizio precedente.
In questo caso possiamo risalire allo stato originario attraverso l’utilizzo dello Stack che ci permette di conservare tutta la storia degli stati.

$JAVA_HOME/bin/java pattern.memento.CareTakerStack
Stato Originator: x
Stato Originator: z
Stato Originator: x

Terzo esempio: vediamo come memorizzare una serie di operazioni di cambio di stato in una coda per poi memorizzarli solo in un secondo momento. Il metodo commitMemento() inserisce tutti gli stati presenti nella coda. Diamo anche la possibilità di eseguire il rollback tramite il metodo rollbackMemento() per cancellare il contenuto della coda di cambi di stati per non apportare le modifiche.

Vediamo come si presenta il pattern in UML in base all’esempio:

Nella classe CaratakerStackQueue è presente uno Stack (pila) ed una Queue (coda). Entrambe sono delle liste di elementi ma la differenza risiede nella logica di inserimento/estrazione dei dati, nel caso di una pila abbiamo un tipo LIFO mentre nel caso di una coda abbiamo un tipo FIFO.
Aggiungiamo gli oggetti Memento nella coda, poi con il metodo commitMemento() inseriamo gli elementi della coda nella pila mentre con il metodo rollbackMemento() svuotiamo la coda.

package pattern.memento;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
import pattern.memento.Originator.Memento;

public class CareTakerStackQueue {

    private static Stack<Memento> mementoStack = new Stack<Memento>();
    private static Queue<Memento> mementoQueue = new LinkedList<Memento>();

    public static void main(String[] args) {
        Originator originator = new Originator();

        originator.setOriginatorState("x");
        //inserisco lo stato attuale nella coda degli stati
        mementoQueue.add( originator.createMemento() );
        System.out.println("Stato Originator: " + originator.getOriginatorState() );

        originator.setOriginatorState("z");
        //inserisco un altro stato nella coda degli stati
        mementoQueue.add( originator.createMemento() );

        //eseguo l'inserimento delle stati
        commitMemento();
        System.out.println("Stato Originator: " + originator.getOriginatorState() );

        //rimuovo l'ultimo stato
        mementoStack.pop();

        //ripristino lo stato precedente
        originator.restoreMemento(mementoStack.pop());
        System.out.println("Stato Originator: " + originator.getOriginatorState() );
    }

    public static void commitMemento() {
        while(!mementoQueue.isEmpty()){
            mementoStack.push( mementoQueue.poll());
        }
    }

    public void rollbackMemento(){
        mementoQueue.clear();
    }
}

L’output ci mostra le modifiche apportate e le operazioni di ripristino dello stato. Si noti che l’inserimento nella coda non determina l’inserimento nello storico degli stati, mentre ciò avverrà solo nel momento in cui verrà invocato il metodo commitMemento() che scorrerà gli elementi presenti nella coda e li inserirà nella pila.

%JAVA_HOME/bin/java pattern.memento.OriginatorStackQueue
Stato Originator: x
Stato Originator: z
Stato Originator: x
Categorie:GOF Pattern, java
  1. SImone
    11 aprile 2012 alle 2:28 PM

    Questi appunti sono perfetti…Complimenti….
    Ma ci sono anche in versione PDF??

    • 13 aprile 2012 alle 9:37 AM

      Ti ringrazio😀
      Ho intenzione di realizzare un PDF quando terminerò tutti i GoF.
      Per adesso è possibile stampare i singoli articoli (“Stampa”) oppure generare il PDF (“Print & PDF”).

  1. No trackbacks yet.
I commenti sono chiusi.
%d blogger cliccano Mi Piace per questo: