Go Patterns
How to Implement Memento Pattern in Go
Behavioral Go Design Pattern
Problem
Let’s take a look at the problem. We have some object and we want to be able to save and restore its state. And we want to have a good design for it.
- We want to have a separation of concerns, different logic has to have different owners.
- We don’t want to break an encapsulation of the object and hide direct access to its state.
The Memento pattern is for the rescue!
Tasks
For example, we have a type that keeps a list of Tasks
. We don’t have direct access to the list of tasks, and we have implemented the Add
method.
type List []string
type Tasks struct {
list List
}
func (t *Tasks) Add(s string) {
t.list = append(t.list, s)
}
func (t *Tasks) All() List {
return t.list
}
Memento
As the first step, we will introduce a new type - Memento
. This is the box that can keep a list of tasks.
This list is also private and we have a specific method to access it.
type Memento struct {
list List
}
func (m *Memento) List() List {
return m.list
}
The Memento
object has to be as simple as possible and it doesn’t have any other logic.
Improve Tasks
The next step will be improving the Tasks
object. We’ll need to implement two additional methods.
Memento()
- it creates aMemento
instance that keeps the currentTasks
state.Restore(m Memento)
- this method can restore aTasks'
state from aMemento
instance
type List []string
type Tasks struct {
list List
}
func (t *Tasks) Add(s string) {
t.list = append(t.list, s)
}
func (t *Tasks) All() List {
return t.list
}
func (t *Tasks) Memento() Memento {
return Memento{list: t.list}
}
func (t *Tasks) Restore(m Memento) {
t.list = m.list
}
History of Changes
The next object we implement will be a History
type. This type keeps a stack of Memento
objects.
Implementing this type is not required by the classical Memento pattern. But I think it’s good to have it for a cleaner example.
type History struct {
history []Memento
}
func NewHistory() *History {
return &History{make([]Memento, 0)}
}
func (h *History) Save(m Memento) {
h.history = append(h.history, m)
}
func (h *History) Undo() Memento {
if len(h.history) > 1 {
n := len(h.history) - 1
h.history = h.history[:n]
return h.history[len(h.history)-1]
} else {
return Memento{}
}
}
Usage Example
In this example, the main
function knows why and when the Tasks
need to save and restore itself.
In the general case, it can be an additional type that keeps that logic.
func main() {
history := NewHistory()
tasks := NewTasks()
tasks.Add("Task 1")
history.Save(tasks.Memento())
tasks.Add("Task 2")
history.Save(tasks.Memento())
fmt.Println(tasks.All())
tasks.Restore(history.Undo())
fmt.Println(tasks.All())
tasks.Restore(history.Undo())
fmt.Println(tasks.All())
}
And output will be
# Output
➜ go-patterns git:(main) go run ./behavioral/memento
[Task 1 Task 2]
[Task 1]
[]
Conclusion
Implementation of a basic Memento Pattern is a good example of separation of concerns. It keeps encapsulation of the initial object and extends its functionality with new logic.
Happy coding!
Code examples can be found in my GitHub repo pavel-fokin/go-patterns.