Go Patterns

How to Implement Memento Pattern in Go

Behavioral Go Design Pattern

Go Memento 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.

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.

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.