C++ - Design pattern - Memento

The Memento design pattern allows an object to retrieve last states of another object.

This is used for example in the undo mechanism.

So it's a very interesting pattern that doesn't use any interface.

But instead it uses an opaque pointer in order to store old states of the main object.

Let's see that in this Memento design pattern tutorial.

First of all

This pattern may seem obscur at first glance and maybe you have some questions such as: Why do we have to use three classes to save states of an object?

Actually there is a good reason for that.

In object oriented programming we use encapsulation and so, to be short, an object can't access private data from another one.

So how to deal with an object that wants to save and restore another object data (even private)?

It's impossible except if we use something like the friend C++ keyword.

Indeed, we are going to tell the Memento object that another object (the Originator) will have the right to manage its data like if it was the Memento itself.

Thus, the Caretaker will have only the role of getting and setting this Memento without knowing which kind of data is inside this Memento.

This is where the opaque pointer will be used.

This private Memento pointer, inside the Caretaker, is completly opaque for the Caretaker.

Only the Originator and the Memento know which kind of data they manipulate.

In this case, we assume that Originator and Memento are classes from an API and we want to hide their definition from the Caretaker.

But as we have a private constructor there is a lack in new C++ versions that won't allow us to compile correctly our code.

Indeed, we can't use a make_unique with a private constructor.

So we have to find another way to achieve that.

This is why I will present you two implementations of the Memento design pattern.

One with the old new keyword and one with unique_ptr.

Note that with the latter we won't use any private constructor but instead a classic public constructor.

We could use a static method to return a unique_ptr but as we need to return an object (with "this" keyword) we won't be able to use it because static methods can't do that.

There is maybe a solution to fix that but I'll try to find it later.

So this last implementation isn't completely optimized, up to you to adapt it to your needs.

Lets' code a bit.

Code with "new"

Caretaker.cpp

#include <iostream>
#include "Caretaker.h"
#include "Memento.h"

// Badprog.com

//-----------------------------------------------------------------------------
// CTOR
//-----------------------------------------------------------------------------
Caretaker::Caretaker() : _lastElementInList(NULL) {
}

//-----------------------------------------------------------------------------
// DTOR
//-----------------------------------------------------------------------------
Caretaker::~Caretaker() {
for (Memento *element : _listMemento) {
delete element;
}
_listMemento.clear();
 
if (NULL != _lastElementInList) {
delete _lastElementInList;
}
}

//-----------------------------------------------------------------------------
// setMemento
//-----------------------------------------------------------------------------
void Caretaker::setMemento(Memento *memento) {
_listMemento.push_back(memento);
}

//-----------------------------------------------------------------------------
// getMemento
//-----------------------------------------------------------------------------
Memento *Caretaker::getMemento() {
_lastElementInList = _listMemento.back();
_listMemento.pop_back();
return _lastElementInList;
}

Caretaker.h

#ifndef __BADPROG_CARETAKER_H__
#define __BADPROG_CARETAKER_H__

#include <list>

// Badprog.com

class Memento;

class Caretaker {
public:
  Caretaker();
  ~Caretaker();

  void setMemento(Memento *memento);
  Memento *getMemento();

private:
  std::list<Memento *> _listMemento;
  Memento *_lastElementInList;
};

#endif // __BADPROG_CARETAKER_H__

main.cpp

#include <memory>
#include <iostream>
#include "Originator.h"
#include "Caretaker.h"

// Badprog.com

//-----------------------------------------------------------------------------
// Main
//-----------------------------------------------------------------------------
int main()
{
    // A
    Originator *originator = new Originator("A", 1);
Caretaker *caretaker = new Caretaker();

    std::cout << "Name = " << originator->getName() << std::endl;
    std::cout << "Age = " << originator->getAge() << std::endl;
caretaker->setMemento(originator->createMemento());

    // B
    originator->setName("B");
    originator->setAge(2);
    std::cout << "Name = " << originator->getName() << std::endl;
    std::cout << "Age = " << originator->getAge() << std::endl;
    caretaker->setMemento(originator->createMemento());

    // C
    originator->setName("C");
    originator->setAge(3);
    std::cout << "Name = " << originator->getName() << std::endl;
    std::cout << "Age = " << originator->getAge() << std::endl;
    caretaker->setMemento(originator->createMemento());

    // D
    originator->setName("D");
    originator->setAge(4);
    std::cout << "Name = " << originator->getName() << std::endl;
    std::cout << "Age = " << originator->getAge() << std::endl;
    caretaker->setMemento(originator->createMemento());
    // Go back
originator->restoreToMemento(caretaker->getMemento());
    std::cout << "Name = " << originator->getName() << std::endl;
    std::cout << "Age = " << originator->getAge() << std::endl;
originator->restoreToMemento(caretaker->getMemento());
    std::cout << "Name = " << originator->getName() << std::endl;
    std::cout << "Age = " << originator->getAge() << std::endl;
originator->restoreToMemento(caretaker->getMemento());
    std::cout << "Name = " << originator->getName() << std::endl;
    std::cout << "Age = " << originator->getAge() << std::endl;
originator->restoreToMemento(caretaker->getMemento());
    std::cout << "Name = " << originator->getName() << std::endl;
    std::cout << "Age = " << originator->getAge() << std::endl;

    delete caretaker;
    delete originator;

return 0;
}

Memento.cpp

#include <iostream>
#include "Memento.h"

// Badprog.com

//-----------------------------------------------------------------------------
// CTOR
//-----------------------------------------------------------------------------
Memento::Memento(Originator originator) : _originator(originator) {
}

//-----------------------------------------------------------------------------
// DTOR
//-----------------------------------------------------------------------------
Memento::~Memento() {
}

//-----------------------------------------------------------------------------
// setState
////-----------------------------------------------------------------------------
void Memento::setOriginator(Originator originator) {
_originator = originator;
}

//-----------------------------------------------------------------------------
// getState
//-----------------------------------------------------------------------------
Originator Memento::getOriginator() {
return _originator;
}

Memento.h

#ifndef __BADPROG_MEMENTO_H__
#define __BADPROG_MEMENTO_H__

// Badprog.com

#include "Memento.h"
#include "Originator.h"

class Memento {
  friend class Originator;

public:
  ~Memento();

private:
  Memento(Originator originator);
  void setOriginator(Originator originator);
  Originator getOriginator();

private:
  Originator _originator;

};

#endif // __BADPROG_MEMENTO_H__

Originator.cpp

#include <iostream>
#include "Originator.h"
#include "Memento.h"

// Badprog.com

//-----------------------------------------------------------------------------
// CTOR
//-----------------------------------------------------------------------------
Originator::Originator(const std::string &name, const int age) :
_name(name), _age(age) {
}

//-----------------------------------------------------------------------------
// DTOR
//-----------------------------------------------------------------------------
Originator::~Originator() {
}

//-----------------------------------------------------------------------------
// setName
//-----------------------------------------------------------------------------
void Originator::setName(const std::string &name) {
_name = name;
}

//-----------------------------------------------------------------------------
// getName
//-----------------------------------------------------------------------------
const std::string Originator::getName() const{
return _name;
}

//-----------------------------------------------------------------------------
// setAge
//-----------------------------------------------------------------------------
void Originator::setAge(const int age) {
_age = age;
}

//-----------------------------------------------------------------------------
// getAge
//-----------------------------------------------------------------------------
const int Originator::getAge() const{
return _age;
}

//-----------------------------------------------------------------------------
// createMemento
//-----------------------------------------------------------------------------
Memento *Originator::createMemento() {
return new Memento(*this);
}

//-----------------------------------------------------------------------------
// restoreToMemento
//-----------------------------------------------------------------------------
void Originator::restoreToMemento(Memento *memento) {
  *this = memento->getOriginator();
}

Originator.h

#ifndef __BADPROG_ORIGINATOR_H__
#define __BADPROG_ORIGINATOR_H__

#include <string>

// Badprog.com

class Memento;

class Originator {
public:
  Originator(const std::string &name, const int age);
  ~Originator();

  void setName(const std::string &name);
  const std::string getName() const ;
  
  void setAge(const int age);
  const int getAge() const;

  Memento *createMemento();
  void restoreToMemento(Memento *memento);

private:
  std::string _name;
  int _age;
};

#endif // __BADPROG_ORIGINATOR_H__

 

Code with "make_unique"

Caretaker.cpp

#include <iostream>
#include <memory>
#include "Caretaker.h"
#include "Originator.h"

// Badprog.com

//-----------------------------------------------------------------------------
// CTOR
//-----------------------------------------------------------------------------
Caretaker::Caretaker() {
}

//-----------------------------------------------------------------------------
// DTOR
//-----------------------------------------------------------------------------
Caretaker::~Caretaker() {
}

//-----------------------------------------------------------------------------
// setMemento
//-----------------------------------------------------------------------------
void Caretaker::setMemento(std::unique_ptr<Memento> memento) {
  _listMemento.push_back(std::move(memento));
}

//-----------------------------------------------------------------------------
// getMemento
//-----------------------------------------------------------------------------
std::unique_ptr<Memento> Caretaker::getMemento() {
  std::unique_ptr<Memento> _lastElementInList;
  _lastElementInList = std::move(_listMemento.back());
  _listMemento.pop_back();
  return _lastElementInList;
}

Caretaker.h

#ifndef __BADPROG_CARETAKER_H__
#define __BADPROG_CARETAKER_H__

#include "Memento.h"
#include <list>

// Badprog.com
class Caretaker {
public:
  Caretaker();
  ~Caretaker();

  void setMemento(std::unique_ptr<Memento> memento);
  std::unique_ptr<Memento> getMemento();

private:
  std::list<std::unique_ptr<Memento>> _listMemento;
};

#endif // __BADPROG_CARETAKER_H__

main.cpp

#include "stdafx.h"

#include <memory>
#include <iostream>
#include "Originator.h"
#include "Caretaker.h"

// Badprog.com

//-----------------------------------------------------------------------------
// Main
//-----------------------------------------------------------------------------
int main()
{
  // A
  auto originator = std::make_unique<Originator>("A", 1);
  auto caretaker = std::make_unique<Caretaker>();

  std::cout << "Name = " << originator->getName() << std::endl;
  std::cout << "Age = " << originator->getAge() << std::endl;
  caretaker->setMemento(originator->createMemento());

  // B
  originator->setName("B");
  originator->setAge(2);
  std::cout << "Name = " << originator->getName() << std::endl;
  std::cout << "Age = " << originator->getAge() << std::endl;
  caretaker->setMemento(originator->createMemento());

  // C
  originator->setName("C");
  originator->setAge(3);
  std::cout << "Name = " << originator->getName() << std::endl;
  std::cout << "Age = " << originator->getAge() << std::endl;
  caretaker->setMemento(originator->createMemento());

  // D
  originator->setName("D");
  originator->setAge(4);
  std::cout << "Name = " << originator->getName() << std::endl;
  std::cout << "Age = " << originator->getAge() << std::endl;
  caretaker->setMemento(originator->createMemento());
  // Go back
  originator->restoreToMemento(caretaker->getMemento());
  std::cout << "Name = " << originator->getName() << std::endl;
  std::cout << "Age = " << originator->getAge() << std::endl;
  originator->restoreToMemento(caretaker->getMemento());
  std::cout << "Name = " << originator->getName() << std::endl;
  std::cout << "Age = " << originator->getAge() << std::endl;
  originator->restoreToMemento(caretaker->getMemento());
  std::cout << "Name = " << originator->getName() << std::endl;
  std::cout << "Age = " << originator->getAge() << std::endl;
  originator->restoreToMemento(caretaker->getMemento());
  std::cout << "Name = " << originator->getName() << std::endl;
  std::cout << "Age = " << originator->getAge() << std::endl;

  return 0;
}

Memento.cpp

#include <iostream>
#include <memory>
#include "Memento.h"

// Badprog.com

//-----------------------------------------------------------------------------
// CTOR
//-----------------------------------------------------------------------------
Memento::Memento(Originator originator) : _originator(originator) {
}

//-----------------------------------------------------------------------------
// DTOR
//-----------------------------------------------------------------------------
Memento::~Memento() {
}

//-----------------------------------------------------------------------------
// setState
////-----------------------------------------------------------------------------
void Memento::setOriginator(Originator originator) {
  _originator = originator;
}

//-----------------------------------------------------------------------------
// getState
//-----------------------------------------------------------------------------
Originator Memento::getOriginator() {
  return _originator;
}

Memento.h

#ifndef __BADPROG_MEMENTO_H__
#define __BADPROG_MEMENTO_H__

#include <string>
#include "Originator.h"

// Badprog.com

class Memento {
  friend class Originator;

public:
  ~Memento();
  Memento(Originator originator);

private:
  void setOriginator(Originator originator);
  Originator getOriginator();

private:
  Originator _originator;
};

#endif // __BADPROG_MEMENTO_H__

Originator.cpp

#include "stdafx.h"
#include <iostream>
#include <memory>
#include "Originator.h"
#include "Memento.h"

// Badprog.com

//-----------------------------------------------------------------------------
// CTOR
//-----------------------------------------------------------------------------
Originator::Originator(const std::string &name, const int age) :
  _name(name), _age(age) {
}

//-----------------------------------------------------------------------------
// DTOR
//-----------------------------------------------------------------------------
Originator::~Originator() {
}

//-----------------------------------------------------------------------------
// setName
//-----------------------------------------------------------------------------
void Originator::setName(const std::string &name) {
  _name = name;
}

//-----------------------------------------------------------------------------
// getName
//-----------------------------------------------------------------------------
const std::string Originator::getName() const {
  return _name;
}

//-----------------------------------------------------------------------------
// setAge
//-----------------------------------------------------------------------------
void Originator::setAge(const int age) {
  _age = age;
}

//-----------------------------------------------------------------------------
// getAge
//-----------------------------------------------------------------------------
const int Originator::getAge() const {
  return _age;
}

//-----------------------------------------------------------------------------
// createMemento
//-----------------------------------------------------------------------------
std::unique_ptr<Memento> Originator::createMemento() {
  return std::make_unique<Memento>(*this);
}

//-----------------------------------------------------------------------------
// restoreToMemento
//-----------------------------------------------------------------------------
void Originator::restoreToMemento(std::unique_ptr<Memento> memento) {
  *this = memento->getOriginator();
}

 

Originator.h

#ifndef __BADPROG_ORIGINATOR_H__
#define __BADPROG_ORIGINATOR_H__

#include <string>

// Badprog.com

class Memento;

//-----------------------------------------------------------------------------
// Originator
//-----------------------------------------------------------------------------
class Originator {
public:
  Originator(const std::string &name, const int age);
  ~Originator();

  void setName(const std::string &name);
  const std::string getName() const;

  void setAge(const int age);
  const int getAge() const;

  std::unique_ptr<Memento> createMemento();
  void restoreToMemento(std::unique_ptr<Memento> memento);

private:
  std::string _name;
  int _age;
};

#endif // __BADPROG_ORIGINATOR_H__

 

Compiling, linking and running

g++ *.cpp -o go.exe -std=c++1y ; ./go.exe

Result

Name = A

Age = 1

Name = B

Age = 2

Name = C

Age = 3

Name = D

Age = 4

Name = D

Age = 4

Name = C

Age = 3

Name = B

Age = 2

Name = A

Age = 1

Conclusion

The result may seem quaint but it shows that we can restore easily the last 4 states of our Originator, respectively name and age: D4, C3, B2 and A1.

Of course, a look at the code is necessary to understand it completely, so don't hesitate to analyze it.

Once again great job you did it. wink

Add new comment

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.