Close
Editor feature with memento design pattern

Memento Design Pattern

Introduction

Memento design pattern states that object state can be externalized and can be restored as needed. This pattern mentions three components which are object of interest i.e. Originator, Memento which wraps the originator and  client that takes care of the memento object called as Caretaker.

Why do you need this pattern?

The pattern can be used in the usecase where the object state needs to be cached and restored later when needed. One example makes this more concrete is the undo/redo feature in text editors.

Let’s see the details of this pattern.

Internals of memento design pattern

There are three main components to this pattern as listed below

  • The Originator is the object whose state needed to be saved.
  • The Memento is the object which keeps the Originator state.
  • The CareTaker(client) which takes care of the Memento objects.

Why Memento? Why cannot the Caretaker directly keep the Originator?

  • This  adds an extra layer of security where clients cannot access the state of the originator at all except any meta data that the Memento object might want to share.

Undo feature of text editors

Functional requirements of undo feature

  • Our bare minimum text editor supports Current  cursor location (X ,Y), character size, screensize, current font, contents on screen.
  • Maximum character size ranges from 1 to 30.
  • We only support Font type Arieal, Times new roman.
  • This undo feature to restore the previous state when the user hits special key work stocks like ctrl + u.
Undo feature of editor using memento design pattern.

Undo feature implementation using Java

Main.java

import working.example.tech.DesignPattern.Behavioural.MementoPattern.*;
public class Main {
    public static void main(String[] args) {
UndoMiniEditorState historyFeature = new UndoMiniEditorState();
    	historyFeature.RunTest();
    }
}

MiniEditor.java

package working.example.tech.DesignPattern.Behavioural.MementoPattern;

public class MiniEditor {
	
    static MiniEditor inst = null;
    private EditorState state;
    
	class EditorState {
		CursorPosition location;
		int CurrentCharSize;
		final ScreenResolution resolution = new ScreenResolution(100, 90);
		Font charFont;
		String word;
		EditorState() {
			location = new CursorPosition(0,0);
			CurrentCharSize = 10;
			word = "";
		}
		
		 EditorState(EditorState clone) {
			 location = new CursorPosition(clone.location);
			 CurrentCharSize = clone.CurrentCharSize;
			 charFont = clone.charFont;
			 word = clone.word;
		 }
	}
	
	enum Font {
		ARIEAL,
		TIMES_NEW_ROMAN
	}
	
	MiniEditor() {
		state = new EditorState();
	}
	
	class ScreenResolution {
		@Override
		public String toString() {
			return "ScreenResolution [width=" + width + ", height=" + height + "]";
		}
		int width;
		int height;
		ScreenResolution(int width, int height) {
			this.width= width;
			this.height = height;
		}
	}
	
    class CursorPosition {
    	@Override
		public String toString() {
			return "CursorPosition [x=" + x + ", y=" + y + "]";
		}

		int x;
    	int y;
    	private CursorPosition(int x, int y) {
    		this.x = x;
    		this.y = y;
    	}
    	
    	private CursorPosition(CursorPosition clone) {
    		this.x = clone.x;
    		this.y = clone.y;
    	}
    	
    	public void updatePosition(int xOffset, int yOffset) {
    		this.x += xOffset;
    		this.y += yOffset;
    	}
    }
    
    private void restoreParameters(MiniEditor.EditorState old) {
    	this.state.location = old.location;
    	this.state.CurrentCharSize = old.CurrentCharSize;
    	this.state.charFont = old.charFont;
    	this.state.word = old.word;
    }
   
    
    public void setFont(Font in) {
    	this.state.charFont = in;
    }
    //This can be improved as per need
    public void writeWord(String word) {
    	this.state.word += word;
    	int len = word.length();
    	if ((len + (this.state.location.x)) >= this.state.resolution.width) {
    		this.state.location.updatePosition((len - this.state.location.x), 1);
    	} else {
    		this.state.location.updatePosition(len, this.state.location.y);
    	}
    }
       

    
    public Snapshot save() {
    	return new Snapshot(new EditorState(this.state));
    }
    
    public void restore(Snapshot snap) {
        this.restoreParameters(snap.getState());  
    }
    
    public static MiniEditor getInstance() {
    	if (inst == null) {
    		inst = new MiniEditor();
    	}
    	return inst;
    }
    
    @Override
	public String toString() {
		return "MiniEditor [location=" + this.state.location + ", CurrentCharSize=" + state.CurrentCharSize + ", resolution="
				+ state.resolution + ", charFont=" + state.charFont + ", ContentsInEditor=" + state.word + "]";
	}

}

Snapshot.java

package working.example.tech.DesignPattern.Behavioural.MementoPattern;

public class Snapshot {
	   private MiniEditor.EditorState state;
	   
	   public MiniEditor.EditorState getState() {
		   return state;
	   }
	   
	   public Snapshot(MiniEditor.EditorState state) {
		   this.state = state;
	   }
}

UndoMiniEditorState.java

package working.example.tech.DesignPattern.Behavioural.MementoPattern;

import java.util.Stack;

public class UndoMiniEditorState implements working.example.tech.interfaces.TestCaseRunner {
	private Stack<Snapshot> history;
	private MiniEditor editor;
    public UndoMiniEditorState() {
    	history = new Stack<>();
    	editor = MiniEditor.getInstance();
    }
	@Override
	public void RunTest() {
		editor.setFont(MiniEditor.Font.ARIEAL);
		editor.writeWord("memento");
		System.out.println("[1] Current state of Editor: " + editor.toString());
		history.push(editor.save());
		
		editor.writeWord(" design");
		System.out.println("[2] Current state of Editor: " + editor.toString());
		history.push(editor.save());
		
		editor.writeWord(" pattern");
		System.out.println("[3] Current state of Editor: " + editor.toString());
		
		editor.restore(history.pop());
		System.out.println("[2] Restoring Previous state"+ editor.toString());
		
		editor.restore(history.pop());
		System.out.println("[1] Restoring Previous state"+ editor.toString());
		
	}
	@Override
	public void showOut() {
		// TODO Auto-generated method stub
		
	}
     
}

Output

[1] Current state of Editor: MiniEditor [location=CursorPosition [x=7, y=0], CurrentCharSize=10, resolution=ScreenResolution [width=100, height=90], charFont=ARIEAL, ContentsInEditor=memento]
[2] Current state of Editor: MiniEditor [location=CursorPosition [x=14, y=0], CurrentCharSize=10, resolution=ScreenResolution [width=100, height=90], charFont=ARIEAL, ContentsInEditor=memento design]
[3] Current state of Editor: MiniEditor [location=CursorPosition [x=22, y=0], CurrentCharSize=10, resolution=ScreenResolution [width=100, height=90], charFont=ARIEAL, ContentsInEditor=memento design pattern]
[2] Restoring Previous state: MiniEditor [location=CursorPosition [x=14, y=0], CurrentCharSize=10, resolution=ScreenResolution [width=100, height=90], charFont=ARIEAL, ContentsInEditor=memento design]
[1] Restoring Previous state: MiniEditor [location=CursorPosition [x=7, y=0], CurrentCharSize=10, resolution=ScreenResolution [width=100, height=90], charFont=ARIEAL, ContentsInEditor=memento]

Conclusion

In this blog we discussed memento design pattern in detail with an example of undo feature. Hope this helps and you will be able to apply this in any of your upcoming features. We have a lot more design patterns to cover so, stay tuned as more blogs coming up!

More blogs