Memento Design Pattern in Java

1. Panoramica

In questo tutorial impareremo cos'è il Memento Design Pattern e come usarlo.

Per prima cosa, esamineremo un po 'di teoria. Quindi, creeremo un esempio in cui illustreremo l'uso del pattern.

2. Cos'è il Memento Design Pattern?

Il Memento Design Pattern, descritto dalla Gang of Four nel loro libro, è un modello di progettazione comportamentale. Il Memento Design Pattern offre una soluzione per implementare azioni annullabili. Possiamo farlo salvando lo stato di un oggetto in un dato istante e ripristinandolo se le azioni eseguite da allora devono essere annullate.

In pratica, l'oggetto il cui stato deve essere salvato è chiamato Originator. Il custode è l'oggetto che attiva il salvataggio e il ripristino dello stato, che si chiama Memento.

L'oggetto Memento dovrebbe esporre il minor numero possibile di informazioni al custode. Questo per garantire che non esponiamo lo stato interno dell'originatore al mondo esterno, poiché infrangerebbe i principi di incapsulamento. Tuttavia, l'originatore dovrebbe accedere a informazioni sufficienti per ripristinare lo stato originale.

Vediamo un rapido diagramma delle classi che illustra come i diversi oggetti interagiscono tra loro:

Come possiamo vedere, l'Originator può produrre e consumare un Memento. Nel frattempo, il custode mantiene solo lo stato prima di ripristinarlo. La rappresentazione interna dell'originatore è tenuta nascosta al mondo esterno.

Qui, abbiamo utilizzato un singolo campo per rappresentare lo stato dell'originatore, sebbene non siamo limitati a un campo e avremmo potuto utilizzare tutti i campi necessari . Inoltre, lo stato contenuto nell'oggetto Memento non deve corrispondere allo stato completo dell'originatore. Finché le informazioni conservate sono sufficienti per ripristinare lo stato dell'originatore, siamo a posto.

3. Quando utilizzare Memento Design Pattern?

In genere, il Memento Design Pattern verrà utilizzato in situazioni in cui alcune azioni sono annullabili, richiedendo quindi il rollback a uno stato precedente. Tuttavia, se lo stato dell'originator è pesante, l'utilizzo del Memento Design Pattern può comportare un costoso processo di creazione e un maggiore utilizzo della memoria.

4. Esempio del motivo ricordo

4.1. Campione iniziale

Vediamo ora un esempio del Memento Design Pattern. Immaginiamo di avere un editor di testo:

public class TextEditor { private TextWindow textWindow; public TextEditor(TextWindow textWindow) { this.textWindow = textWindow; } }

Ha una finestra di testo, che contiene il testo attualmente inserito e fornisce un modo per aggiungere altro testo:

public class TextWindow { private StringBuilder currentText; public TextWindow() { this.currentText = new StringBuilder(); } public void addText(String text) { currentText.append(text); } }

4.2. Memento

Ora, immaginiamo di volere che il nostro editor di testo implementi alcune funzionalità di salvataggio e annullamento. Durante il salvataggio, vogliamo che il nostro testo corrente venga salvato. Pertanto, quando si annullano le modifiche successive, il testo salvato verrà ripristinato.

Per fare ciò, utilizzeremo il Memento Design Pattern. Per prima cosa, creeremo un oggetto contenente il testo corrente della finestra:

public class TextWindowState { private String text; public TextWindowState(String text) { this.text = text; } public String getText() { return text; } }

Questo oggetto è il nostro Memento. Come possiamo vedere, scegliamo di utilizzare String invece di StringBuilder per impedire qualsiasi aggiornamento del testo corrente da parte di estranei.

4.3. Originatore

Dopodiché, dovremo fornire alla classe TextWindow metodi per creare e consumare l'oggetto Memento, rendendo la TextWindow il nostro Originatore:

public TextWindowState save() { return new TextWindowState(wholeText.toString()); } public void restore(TextWindowState save) { currentText = new StringBuilder(save.getText()); }

Il metodo save () ci permette di creare l'oggetto, mentre il metodo restore () lo consuma per ripristinare lo stato precedente.

4.4. Custode

Infine, dobbiamo aggiornare la nostra classe TextEditor . In qualità di custode, manterrà lo stato dell'originatore e chiederà di ripristinarlo quando necessario:

private TextWindowState savedTextWindow; public void hitSave() { savedTextWindow = textWindow.save(); } public void hitUndo() { textWindow.restore(savedTextWindow); }

4.5. Testare la soluzione

Vediamo se funziona attraverso un'esecuzione di esempio. Immagina di aggiungere del testo al nostro editor, salvarlo, quindi aggiungerne altro e, infine, annullare. Per ottenere ciò, aggiungeremo un metodo print () sul nostro TextEditor che restituisce una stringa del testo corrente:

TextEditor textEditor = new TextEditor(new TextWindow()); textEditor.write("The Memento Design Pattern\n"); textEditor.write("How to implement it in Java?\n"); textEditor.hitSave(); textEditor.write("Buy milk and eggs before coming home\n"); textEditor.hitUndo(); assertThat(textEditor.print()).isEqualTo("The Memento Design Pattern\nHow to implement it in Java?\n");

Come possiamo vedere, l'ultima frase non fa parte del testo corrente, poiché il Memento è stato salvato prima di aggiungerlo.

5. conclusione

In questo breve articolo, abbiamo spiegato il Memento Design Pattern e per cosa può essere utilizzato. Abbiamo anche esaminato un esempio che illustra il suo utilizzo in un semplice editor di testo.

Il codice completo utilizzato in questo articolo può essere trovato su GitHub.