Il problema: tornare indietro senza rompere l'incapsulamento
Un editor di testo deve supportare l'undo. Per annullare un'operazione, serve lo stato precedente del documento. La soluzione ingenua e rendere pubbliche tutte le proprieta dell'editor per poterle salvare dall'esterno. Ma questo viola l'incapsulamento: il codice che gestisce l'undo conosce i dettagli interni dell'editor, e se la struttura interna cambia, il sistema di undo si rompe.
Il Memento Pattern risolve il dilemma: l'oggetto stesso crea uno snapshot del proprio stato (il memento) e lo fornisce all'esterno come oggetto opaco. Chi gestisce la history (il caretaker) conserva i memento senza poterne leggere il contenuto. Solo l'oggetto originale sa come ripristinare il proprio stato da un memento.
Cos'e il Memento Pattern: definizione formale
Il Gang of Four definisce il Memento come un pattern comportamentale che "senza violare l'incapsulamento, cattura e esternalizza lo stato interno di un oggetto in modo che l'oggetto possa essere ripristinato a questo stato in seguito". Tre attori:
-
Originator: l'oggetto il cui stato deve essere salvato (es. l'editor). Ha metodi
createMemento()erestore(Memento $m) - Memento: l'oggetto opaco che contiene lo snapshot dello stato. Espone il contenuto solo all'Originator.
- Caretaker: gestisce la collection di memento (la history). Non accede mai al contenuto dei memento.
Esempio teorico: undo/redo in un editor grafico
Un editor grafico ha un canvas con forme (rettangoli, cerchi, linee). Ogni azione dell'utente (spostare una forma, cambiare colore, ridimensionare) modifica lo stato. Il sistema di undo funziona così:
- Prima di ogni azione, il canvas chiama
createMemento()che salva le posizioni, dimensioni e colori di tutte le forme - Il caretaker (un
HistoryManager) pusha il memento nello stack di undo - L'utente preme Ctrl+Z: il caretaker poppa l'ultimo memento e chiama
canvas->restore($memento) - Il canvas legge lo stato dal memento e si riporta alla configurazione precedente
- Il memento poppato va nello stack di redo per permettere Ctrl+Y
Memento e serializzazione in PHP
In PHP, il Memento si implementa spesso tramite serializzazione. Il memento può essere un array associativo dello stato, un oggetto immutabile, o persino una stringa JSON. L'importante e che il contenuto sia opaco per il caretaker:
-
Array memento:
createMemento(): arrayrestituisce['title' => $this->title, 'content' => $this->content]. Semplice ma espone la struttura. -
Oggetto memento: una classe
EditorMementocon proprieta private. Solo l'editor sa come leggerle. Più sicuro. - Memento criptato: lo stato viene serializzato e criptato. Anche se il caretaker accede al memento, non può leggerne il contenuto.
Memento vs Command per l'undo
- Command con undo: ogni command sa come annullarsi. L'undo e inverso: esegue l'operazione opposta. Funziona se ogni operazione ha un inverso chiaro.
- Memento: salva lo stato completo prima di ogni operazione. L'undo e ripristino: riporta allo stato salvato. Funziona anche quando l'operazione inversa non esiste o e ambigua.
Il Command e più efficiente (salva solo il delta), il Memento e più robusto (salva tutto). Per operazioni semplici e reversibili usa Command. Per stati complessi dove l'inverso e difficile da calcolare, usa Memento.
Quando usare il Memento Pattern
- Usa il Memento quando devi implementare undo/redo e lo stato dell'oggetto e complesso
- Usa il Memento quando vuoi salvare checkpoint/snapshot dello stato per recovery
- Usa il Memento quando l'accesso diretto allo stato violerebbe l'incapsulamento
- Non usare il Memento se lo stato e enorme e i memento consumano troppa memoria — considera il salvataggio incrementale
- Non usare il Memento se l'operazione inversa e semplice e chiara: Command con undo e più leggero
Il Memento Pattern e il pattern della fotografia dello stato: cattura un istante, lo conserva, e lo ripristina quando serve. E il fondamento di ogni sistema di undo, versioning e recovery che rispetti l'incapsulamento degli oggetti.
Top comments (0)