A guide to software design patterns
Behavioral Design Pattern: Memento
The Memento pattern is one of the eleven behavioral design patterns. Memento pattern is used to restore an object to its previous state. It is also known as a snapshot pattern. This is simply a way of tracking states of an object and having the ability to revert previous changes.
Implementing this pattern requires three objects:
- originator
- memento
- caretaker
The originator is the object we’ll be making changes to. It provides a way to create and restore mementos.
The memento is a snapshot of the originator at some point in time. Mementos can only be created and read by the originator and cannot be changed once created — i.e. mementos are immutable.
The caretaker is the object that performs “create” and “restore” memento operations on the originator object. Think of the caretaker as an administrator that is capable of taking snapshots (mementos) of a system (originator) and restores those snapshots at will.
Pattern Ideal Usage
When would this pattern be a good solution? Let’s say you’re designing a real estate system that must provide agents with the ability to change and restore a property’s price. Ideally, every time a price change happened, we’d create a memento to be able to revert. We need to create three objects:
- PropertyMemento — memento
- Property — originator
- Agent — caretaker
Both PropertyMemento and Property classes will have getters, but we’ll only have setters in the Property class to make sure the PropertyMemento is immutable. For brevity, we won’t be doing any validation, we’ll just assume we’ll always get the right values.
Here’s the code for the PropertyMemento class:
public class PropertyMemento {
private final UUID id;
private final String address;
private final double price;
private final String phone;
private final String date; public PropertyMemento(UUID id,
String address,
double price,
String phone,
String date){
this.id = id;
this.address = address;
this.price = price;
this.phone = phone;
this.date = date;
} public UUID getId() {
return id;
} public String getAddress() {
return address;
} public double getPrice() {
return price;
} public String getPhone() {
return phone;
} public String getDate() {
return date;
} @Override
public String toString() {
return "PropertyMemento {\n" +
"\tid => " + id + "\n" +
"\taddress => " + address +"\n" +
"\tprice => " + price + "\n" +
"\tphone => " + phone + "\n" +
"\tdate => " + date + "\n" +
'}';
}
}Here’s the code for the Property class:
public class Property {
private UUID id;
private String address;
private double price;
private String phone;
private String date; private final DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss"); public Property(String address,
double price, String phone){
this.id = UUID.randomUUID();
this.address = address;
this.price = price;
this.phone = phone;
this.date = formatter.format(LocalDateTime.now());
} public void setPrice(double price){
this.price = price;
} public void setPhone(String phone){
this.phone = phone;
} public void setAddress(String address){
this.address = address;
} public PropertyMemento create(){
/**
* set date to current datatime
* whenever a memento gets created
*/
return new PropertyMemento(this.id,
this.address,
this.price,
this.phone,
formatter.format(LocalDateTime.now()));
} public void restore(PropertyMemento memento){
this.id = memento.getId();
this.address = memento.getAddress();
this.price = memento.getPrice();
this.phone = memento.getPhone();
this.date = memento.getDate();
} @Override
public String toString() {
return "Property {\n" +
"\tid => " + id + "\n" +
"\taddress => " + address +"\n" +
"\tprice => " + price + "\n" +
"\tphone => " + phone + "\n" +
"\tdate => " + date + "\n" +
'}' + "\n";
}
}I’ve overridden toString() method to print a format of my liking, you can change it according to whatever object you’re working with.
The Agent class will have a list of all create mementos methods to set the price of the property, create, restore, and get all created mementos.
Here’s the code for the Agent class:
public class Agent {
private List<PropertyMemento> mementos = new ArrayList<>();
private Property originator; public Agent(Property originator){
this.originator = originator;
} public void setPrice(double price){
System.out.println(
String.format("Setting price to %.2f",price));
this.originator.setPrice(price);
} public void setPhone(String phone){
System.out.println(
String.format("Setting phone to %s", phone));
this.originator.setPhone(phone);
} public void setAddress(String address){
System.out.println(
String.format("Setting address to %s", address));
this.originator.setAddress(address);
} public PropertyMemento createMemento(){
System.out.println("Creating memento...");
PropertyMemento memento = this.originator.create();
this.mementos.add(memento);
return memento;
} public List<PropertyMemento> getMementos(){
System.out.println("Retrieving mementos...");
return this.mementos;
} public void restoreMemento(PropertyMemento memento){
System.out.println("Restoring memento...");
this.originator.restore(memento);
}
}We can use the Agent class in your main method to simulate an agent behavior like this.
public static void main(String[] args) {
//originator
Property property = new Property(
"1234 Imaginary Street, San Francisco, CA 94105",
2500000d,
"999-876-5432"); //Caretaker
Agent agent = new Agent(property);
agent.setPrice(2700000d);
agent.createMemento();
agent.setPhone("999-234-5678");
agent.createMemento();
agent.setAddress("1234 Real Street, San Francisco, CA 94105");
agent.createMemento();
agent.setPrice(2300000d);
agent.createMemento(); List<PropertyMemento> mementos = agent.getMementos();
System.out.println("\n----Current State----");
System.out.println(property); //restoring to mementos
agent.restoreMemento(mementos.get(0));
System.out.println(property); agent.restoreMemento(mementos.get(2));
System.out.println(property); agent.restoreMemento(mementos.get(1));
System.out.println(property);
}Your output window now looks like this:
Setting price to 2700000.00
Creating memento...
Setting phone to 999-234-5678
Creating memento...
Setting address to 1234 Real Street, San Francisco, CA 94105
Creating memento...
Setting price to 2300000.00
Creating memento...
Retrieving mementos...----Current State----
Property {
id => 25beba9c-430a-4b80-a1ae-9ee1d601d867
address => 1234 Real Street, San Francisco, CA 94105
price => 2300000.0
phone => 999-234-5678
date => 02/07/2020 20:38:15
}Restoring memento...
Property {
id => 25beba9c-430a-4b80-a1ae-9ee1d601d867
address => 1234 Imaginary Street, San Francisco, CA 94105
price => 2700000.0
phone => 999-876-5432
date => 02/07/2020 20:38:15
}Restoring memento...
Property {
id => 25beba9c-430a-4b80-a1ae-9ee1d601d867
address => 1234 Real Street, San Francisco, CA 94105
price => 2700000.0
phone => 999-234-5678
date => 02/07/2020 20:38:15
}Restoring memento...
Property {
id => 25beba9c-430a-4b80-a1ae-9ee1d601d867
address => 1234 Imaginary Street, San Francisco, CA 94105
price => 2700000.0
phone => 999-234-5678
date => 02/07/2020 20:38:15
}It’s important to point out that the date value is the same for all the created mementos because I’m using a fast machine. You can delay the execution to see different times in the mementos.
That is pretty much the memento pattern in a nutshell. Thank you for making it to the end.
Happy coding.
Previous: Behavioral Design Pattern: Chain of Responsibility
