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.
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 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:
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.
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:
With these two simple requirements, we can already start writing some tests for this example.
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.
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.
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.
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.
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.
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.
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.
18.216.255.250