Memento design pattern

Let's now look at a pattern with a fancy name. If we check a dictionary to see the meaning of memento, we will find the following description:

"An object kept as a reminder of a person or event."

Here, the key word is reminder as we will remember actions with this design pattern.

Description

The meaning of memento is very similar to the functionality it provides in design patterns. Basically, we'll have a type with some state and we want to be able to save milestones of its state. Having a finite amount of states saved, we can recover them if necessary for a variety of tasks-undo operations, historic, and so on.

The Memento design pattern usually has three players (usually called actors):

  • Memento: A type that stores the type we want to save. Usually, we won't store the business type directly and we provide an extra layer of abstraction through this type.
  • Originator: A type that is in charge of creating mementos and storing the current active state. We said that the Memento type wraps states of the business type and we use originator as the creator of mementos.
  • Care Taker: A type that stores the list of mementos that can have the logic to store them in a database or to not store more than a specified number of them.

Objectives

Memento is all about a sequence of actions over time, say to undo one or two operations or to provide some kind of transactionality to some application.

Memento provides the foundations for many tasks, but its main objectives could be defined as follows:

  • Capture an object state without modifying the object itself
  • Save a limited amount of states so we can retrieve them later

A simple example with strings

We will develop a simple example using a string as the state we want to save. This way, we will focus on the common Memento pattern implementations before making it a bit more complex with a new example.

The string, stored in a field of a State instance, will be modified and we will be able to undo the operations done in this state.

Requirements and acceptance criteria

We are constantly talking about state; all in all, the Memento pattern is about storing and retrieving states. Our acceptance criteria must be all about states:

  1. We need to store a finite amount of states of type string.
  2. We need a way to restore the current stored state to one of the state list.

With these two simple requirements, we can already start writing some tests for this example.

Unit test

As mentioned previously, the Memento design pattern is usually composed of three actors: state, memento, and originator. So we will need three types to represent these actors:

type State struct { 
  Description string 
} 

The State type is the core business object we will be using during this example. It's any kind of object that we want to track:

type memento struct { 
  state State 
} 

The memento type has a field called state representing a single value of a State type. Our states will be containerized within this type before storing them into the care taker type. You could be wondering why we don't store directly State instances. Basically, because it will couple the originator and the careTaker to the business object and we want to have as little coupling as possible. It will also be less flexible, as we will see in the second example:

type originator struct { 
  state State 
} 
 
func (o *originator) NewMemento() memento { 
  return memento{} 
} 
 
func (o *originator) ExtractAndStoreState(m memento) { 
  //Does nothing 
} 

The originator type also stores a state. The originator struct's objects will take states from mementos and create new mementos with their stored state.

Tip

What's the difference between the originator object and the Memento pattern? Why don't we use Originator pattern's object directly? Well, if the Memento contains a specific state, the originator type contains the state that is currently loaded. Also, to save the state of something could be as simple as to take some value or as complex as to maintain the state of some distributed application.

The Originator will have two public methods--the NewMemento() method and the ExtractAndStoreState(m memento) method. The NewMemento method will return a new Memento built with originator current State value. The ExtractAndStoreState method will take the state of a Memento and store it in the Originator's state field:

type careTaker struct { 
  mementoList []memento 
} 
 
func (c *careTaker) Add(m memento) { 
  //Does nothing 
} 
 
func (c *careTaker) Memento(i int) (memento, error) { 
  return memento{}, fmt.Errorf("Not implemented yet") 
} 

The careTaker type stores the Memento list with all the states we need to save. It also stores an Add method to insert a new Memento on the list and a Memento retriever that takes an index on the Memento list.

So let's start with the Add method of the careTaker type. The Add method must take a memento object and add it to the careTaker object's list of Mementos:

func TestCareTaker_Add(t *testing.T) { 
  originator := originator{} 
  originator.state = State{Description:"Idle"} 
 
  careTaker := careTaker{} 
  mem := originator.NewMemento() 
  if mem.state.Description != "Idle" { 
    t.Error("Expected state was not found") 
  } 

At the beginning of our test, we created two basic actors for memento--the originator and the careTaker. We set a first state on the originator with the description Idle.

Then, we create the first Memento calling the NewMemento method. This should wrap the current originator's state in a memento type. Our first check is very simple--the state description of the returned Memento must be like the state description we pass to the originator, that is, the Idle description.

The last step to check whether our Memento's Add method works correctly is to see whether the Memento list has grown after adding one item:

  currentLen := len(careTaker.mementoList) 
  careTaker.Add(mem) 
 
  if len(careTaker.mementoList) != currentLen+1 { 
    t.Error("No new elements were added on the list") 
  } 

We also have to test the Memento(int) memento method. This should take a memento value from the careTaker list. It takes the index you want to retrieve from the list so, as usual with lists, we must check that it behaves correctly against negative numbers and out of index values:

func TestCareTaker_Memento(t *testing.T) { 
  originator := originator{} 
  careTaker := careTaker{} 
 
  originator.state = State{"Idle"} 
  careTaker.Add(originator.NewMemento()) 

We have to start like we did in our previous test--creating an originator and careTaker objects and adding the first Memento to the caretaker:

  mem, err := careTaker.Memento(0) 
  if err != nil { 
    t.Fatal(err) 
  } 
 
  if mem.state.Description != "Idle" { 
    t.Error("Unexpected state") 
  } 

Once we have the first object on the careTaker object, we can ask for it using careTaker.Memento(0). Index 0 on the Memento(int) method retrieves the first item on the slice (remember that slices start with 0). No error should be returned because we have already added a value to the caretaker object.

Then, after retrieving the first memento, we checked that the description matches the one that we passed at the beginning of the test:

  mem, err = careTaker.Memento(-1) 
  if err == nil { 
    t.Fatal("An error is expected when asking for a negative number but no error was found") 
  } 
} 

The last step on this test involves using a negative number to retrieve some value. In this case, an error must be returned that shows that no negative numbers can be used. It is also possible to return the first index when you pass negative numbers but here we will return an error.

The last function to check is the ExtractAndStoreState method. This function must take a Memento and extract all its state information to set it in the Originator object:

func TestOriginator_ExtractAndStoreState(t *testing.T) { 
  originator := originator{state:State{"Idle"}} 
  idleMemento := originator.NewMemento() 
 
  originator.ExtractAndStoreState(idleMemento) 
  if originator.state.Description != "Idle" { 
    t.Error("Unexpected state found") 
  } 
} 

This test is simple. We create a default originator variable with an Idle state. Then, we retrieve a new Memento object to use it later. We change the state of the originator variable to the Working state to ensure that the new state will be written.

Finally, we have to call the ExtractAndStoreState method with the idleMemento variable. This should restore the state of the originator to the idleMemento state's value, something that we checked in the last if statement.

Now it's time to run the tests:

go test -v .         
=== RUN   TestCareTaker_Add
--- FAIL: TestCareTaker_Add (0.00s)
  memento_test.go:13: Expected state was not found
  memento_test.go:20: No new elements were added on the list
=== RUN   TestCareTaker_Memento
--- FAIL: TestCareTaker_Memento (0.00s)
  memento_test.go:33: Not implemented yet
=== RUN   TestOriginator_ExtractAndStoreState
--- FAIL: TestOriginator_ExtractAndStoreState (0.00s)
  memento_test.go:54: Unexpected state found
FAIL
exit status 1
FAIL

Because the three tests fail, we can continue with the implementation.

Implementing the Memento pattern

The Memento pattern's implementation is usually very simple if you don't get too crazy. The three actors (memento, originator, and care taker) have a very defined role in the pattern and their implementation is very straightforward:

type originator struct { 
  state State 
} 
 
func (o *originator) NewMemento() memento { 
  return memento{state: o.state} 
} 
 
func (o *originator) ExtractAndStoreState(m memento) { 
  o.state = m.state 
} 

The Originator object needs to return a new values of Memento types when calling the NewMemento method. It also needs to store the value of a memento object in the state field of the struct as needed for the ExtractAndStoreState method:

type careTaker struct { 
  mementoList []memento 
} 
 
func (c *careTaker) Push(m memento) { 
  c.mementoList = append(c.mementoList, m) 
} 
 
func (c *careTaker) Memento(i int) (memento, error) { 
  if len(c.mementoList) < i || i < 0 { 
    return memento{}, fmt.Errorf("Index not found
") 
  } 
  return c.mementoList[i], nil 
} 

The careTaker type is also straightforward. When we call the Add method, we overwrite the mementoList field by calling the append method with the value passed in the argument. This creates a new list with the new value included.

When calling the Memento method, we have to do a couple of checks beforehand. In this case, we check that the index is not outside of the range of the slice and that the index is not a negative number in the if statement, in which case we return an error. If everything goes fine, it just returns the specified memento object and no errors.

Tip

A note about method and function naming conventions. You could find some people that like to give slightly more descriptive names to methods such as Memento. An example would be to use a name such as MementoOrError method, clearly showing that you return two objects when calling this function or even GetMementoOrError method. This could be a very explicit approach for naming and it's not necessarily bad, but you won't find it very common in Go's source code.

Time to check the test results:
go test -v .
=== RUN   TestCareTaker_Add
--- PASS: TestCareTaker_Add (0.00s)
=== RUN   TestCareTaker_Memento
--- PASS: TestCareTaker_Memento (0.00s)
=== RUN   TestOriginator_ExtractAndStoreState
--- PASS: TestOriginator_ExtractAndStoreState (0.00s)
PASS
ok

That was enough to reach 100% of coverage. While this is far from being a perfect metric, at least we know that we are reaching every corner of our source code and that we haven't cheated in our tests to achieve it.

Another example using the Command and Facade patterns

The previous example is good and simple enough to understand the functionality of the Memento pattern. However, it is more commonly used in conjunction with the Command pattern and a simple Facade pattern.

The idea is to use a Command pattern to encapsulate a set of different types of states (those that implement a Command interface) and provide a small facade to automate the insertion in the caretaker object.

We are going to develop a small example of a hypothetical audio mixer. We are going to use the same Memento pattern to save two types of states: Volume and Mute. The Volume state is going to be a byte type and the Mute state a Boolean type. We will use two completely different types to show the flexibility of this approach (and its drawbacks).

As a side note, we can also ship each Command interface with their own serialization methods on the interface. This way, we can give the ability to the caretaker to store states in some kind of storage without really knowing what's storing.

Our Command interface is going to have one method to return the value of its implementer. It's very simple, every command in our audio mixer that we want to undo will have to implement this interface:

type Command interface { 
  GetValue() interface{} 
} 

There is something interesting in this interface. The GetValue method returns an interface to a value. This also means that the return type of this method is... well... untyped? Not really, but it returns an interface that can be a representation of any type and we will need to typecast it later if we want to use its specific type. Now we have to define the Volume and Mute types and implement the Command interface:

type Volume byte 
 
func (v Volume) GetValue() interface{} { 
  return v 
} 
 
type Mute bool 
 
func (m Mute) GetValue() interface{} { 
  return m 
} 

They are both quite easy implementations. However, the Mute type will return a bool type on the GetValue() method and Volume will return a byte type.

As in the previous example, we'll need a Memento type that will hold a Command. In other words, it will store a pointer to a Mute or a Volume type:

type Memento struct { 
  memento Command 
} 

The originator type works as in the previous example but uses the Command keyword instead of the state keyword:

type originator struct { 
  Command Command 
} 
 
func (o *originator) NewMemento() Memento { 
  return Memento{memento: o.Command} 
} 
 
func (o *originator) ExtractAndStoreCommand(m Memento) { 
  o.Command = m.memento 
} 

And the caretaker object is almost the same, but this time we'll use a stack instead of a simple list and we will store a command instead of a state:

type careTaker struct { 
  mementoList []Memento 
} 
 
func (c *careTaker) Add(m Memento) { 
  c.mementoList = append(c.mementoList, m) 
} 
 
func (c *careTaker) Pop() Memento { 
  if len(c.mementoStack) > 0 { 
    tempMemento := c.mementoStack[len(c.mementoStack)-1] 
    c.mementoStack = c.mementoStack[0:len(c.mementoStack)-1] 
    return tempMemento 
  } 
 
  return Memento{} 
} 

However, our Memento list is replaced with a Pop method. It also returns a memento object but it will return them acting as a stack (last to enter, first to go out). So, we take the last element on the stack and store it in the tempMemento variable. Then we replace the stack with a new version that doesn't contain the last element on the next line. Finally, we return the tempMemento variable.

Until now, everything looks almost like in the previous example. We also talked about automating some tasks by using the Facade pattern, so let's do it. This is going to be called the MementoFacade type and will have the SaveSettings and RestoreSettings methods. The SaveSettings method takes a Command, stores it in an inner originator, and saves it in an inner careTaker field. The RestoreSettings method makes the opposite flow-restores an index of the careTaker and returns the Command inside the Memento object:

type MementoFacade struct { 
  originator originator 
  careTaker  careTaker 
} 
 
func (m *MementoFacade) SaveSettings(s Command) { 
  m.originator.Command = s 
  m.careTaker.Add(m.originator.NewMemento()) 
} 
 
func (m *MementoFacade) RestoreSettings(i int) Command { 
  m.originator.ExtractAndStoreCommand(m.careTaker.Memento(i)) 
  return m.originator.Command 
} 

Our Facade pattern will hold the contents of the originator and the care taker and will provide those two easy-to-use methods to save and restore settings.

So, how do we use this?

func main(){ 
  m := MementoFacade{} 
 
  m.SaveSettings(Volume(4)) 
  m.SaveSettings(Mute(false)) 

First, we get a variable with a Facade pattern. Zero-value initialization will give us zero-valued originator and caretaker objects. They don't have any unexpected field so everything will initialize correctly (if any of them had a pointer, for example, it would be initialized to nil as mentioned in the Zero initialization section of Chapter 1, Ready... Steady... Go!).

We create a Volume value with Volume(4) and, yes, we have used parentheses. The Volume type does not have any inner field like structs so we cannot use curly braces to set its value. The way to set it is to use parentheses (or create a pointer to the type Volume and then set the value of the pointed space). We also save a value of the type Mute using the Facade pattern.

We don't know what Command type is returned here, so we need to make a type assertion. We will make a small function to help us with this that checks the type and prints an appropriate value:

func assertAndPrint(c Command){ 
  switch cast := c.(type) { 
  case Volume: 
    fmt.Printf("Volume:	%d
", cast) 
  case Mute: 
    fmt.Printf("Mute:	%t
", cast) 
  } 
} 

The assertAndPrint method takes a Command type and casts it to the two possible types-Volume or Mute. In each case, it prints a message to the console with a personalized message. Now we can continue and finish the main function, which will look like this:

func main() { 
  m := MementoFacade{} 
 
  m.SaveSettings(Volume(4)) 
  m.SaveSettings(Mute(false)) 
 
  assertAndPrint(m.RestoreSettings(0))
    
  assertAndPrint(m.RestoreSettings(1)) 
} 

The part highlighted in bold shows the new changes within the main function. We took the index 0 from the careTaker object and passed it to the new function and the same with the index 1. Running this small program, we should get the Volume and Mute values on the console:

$ go run memento_command.go
Mute:   false
Volume: 4

Great! In this small example, we have combined three different design patterns to keep getting comfortable using various patterns. Keep in mind that we could have abstracted the creation of Volume and Mute states to a Factory pattern too so this is not where would stop.

Last words on the Memento pattern

With the Memento pattern, we have learned a powerful way to create undoable operations that are very useful when writing UI applications but also when you have to develop transactional operations. In any case, the situation is the same: you need a Memento, an Originator, and a caretaker actor.

Tip

A transaction operation is a set of atomic operations that must all be done or fail. In other words, if you have a transaction composed of five operations and just one of them fails, the transaction cannot be completed and every modification done by the other four must be undone.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.11.240