diff --git a/CMakeLists.txt b/CMakeLists.txt index 24e1d05..357f5ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,7 @@ set(APP_SOURCES "src/patterns/behavioral/Command.cpp" "src/patterns/behavioral/Iterator.cpp" "src/patterns/behavioral/Mediator.cpp" + "src/patterns/behavioral/Memento.cpp" ) # Test files diff --git a/docs/uml/patterns_structural_memento.drawio.svg b/docs/uml/patterns_structural_memento.drawio.svg new file mode 100644 index 0000000..9c37849 --- /dev/null +++ b/docs/uml/patterns_structural_memento.drawio.svg @@ -0,0 +1,4 @@ + + + +
main:
{
Originator* originator = new Originator(init_state);
clientCode(originator );
}

clientCode:
{
 CareTaker* careTaker = new CareTaker(org);

 careTaker->backup();
 org->operation();

 careTaker->backup();
 org->operation();

 careTaker->history();
 careTaker->redo();
 careTaker->redo();
}
Client
+ clientCode(Originator* org): type
Use
Originator
- state: type
+ operation(type): type
+ save(): IMemento*
+ restore(IMemento* mem): type

<<Interface>>
IMemento


+ getState(): type
+ getName(): type

ConcreteMemento
- name: string
- state: type

ConcreteMemento(type state)
CareTaker
- originator: Originator*
- mementos: collection<IMemento*>
+ ~CareTaker()

+ backup(): type
+ undo(): type
+ history(): type
Use
Use
1
0..*
save:
{
 return new ConcreteMemonto(this->state);
}

restore:
{
 this->state = mem->getState();
}
backup:
{
 this->mementos.add(this->originator->save());
}

restore:
{
 if(this->mementos.size() == 0){
  return;
 }

 IMemento* mem = this->mementos.back();
 this->mementos.pop_back();
 
 this->originator->restore(mem);
}

history:
for(IMemento* mem: this->mementos){
 mem->getName();
}
\ No newline at end of file diff --git a/src/patterns/behavioral/Memento.cpp b/src/patterns/behavioral/Memento.cpp new file mode 100644 index 0000000..cfca295 --- /dev/null +++ b/src/patterns/behavioral/Memento.cpp @@ -0,0 +1,235 @@ +// Memento is a behavioral design pattern that lets you save and restore the previous state of an object +// without violating encapsulation, captures and externalizes an object's internal state +// Appicability: +// (*) when you want to produce snapshots of the object’s state to be able to restore a previous state of the object. +// (**) when direct access to the object’s fields/getters/setters violates its encapsulation. +// UML: docs/uml/patterns_behavioral_memento.drawio.svg + +#include +#include +#include // for std::time +#include // for std::rand, std::srand +#include +#include +#include +namespace +{ + namespace Memento + { + /** + * Memento interface provides a way to retrieve the memento's metadata, such as creation date or name. + * However, it doesn't expose the Originator's state. + */ + class IMemento + { + public: + virtual ~IMemento() = default; + + virtual std::string getName() const = 0; + virtual std::string getDate() const = 0; + virtual std::string getState() const = 0; + }; + + /** + * Concrete Memento contains the infrastructure for storing the Originator's state. + */ + class ConcreteMemento : public IMemento + { + private: + std::string m_state; + std::string m_date; + std::string m_name; + + public: + explicit ConcreteMemento(const std::string &state) : m_state{state} + { + // Get current time + std::time_t now = std::time(nullptr); + std::tm *t = std::localtime(&now); + + // Format date as YYYYMMDD_HHMMSS + std::stringstream date_ss; + date_ss << std::put_time(t, "%Y%m%d_%H%M%S"); + m_date = date_ss.str(); + + // Append a random number for uniqueness + int rand_num = std::rand() % 10000; // optional: limit size + std::stringstream name_ss; + name_ss << "mem_" << m_date << "_" << rand_num; + m_name = name_ss.str(); + } + + std::string getName() const override + { + return m_name; + }; + + std::string getDate() const override + { + return this->m_date; + }; + + std::string getState() const override + { + return this->m_state; + } + }; + + /** + * Originator holds some important state that may change over time. + * It also defines a method for saving the state inside a memento and another method for + * restoring the state from it. + */ + class Originator + { + private: + std::string m_state; + + // Simulate new state using rand + static std::string generateRandomString(int len = 10) + { + // String literal concatenation + const char alphaNum[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + + int strLen = sizeof(alphaNum) - 1; + std::string ranStr; + for (int i = 0; i < len; ++i) + { + ranStr += alphaNum[std::rand() % strLen]; + } + return ranStr; + } + + public: + explicit Originator(const std::string &state) : m_state{state} + { + std::cout << "[O]Initial state is: " << this->m_state << "\n"; + } + + void operation() + { + std::cout << "[O]Doing something important.\n"; + this->m_state = this->generateRandomString(30); + std::cout << "[O]The state has changed to: " << this->m_state << "\n"; + } + + // Save the current state inside a memento. + IMemento *save() + { + return new ConcreteMemento(this->m_state); + } + + // Restores the Originator's state from a memento object. + void restore(IMemento *mem) + { + this->m_state = mem->getState(); + std::cout << "[O]The state has restored to: " << this->m_state << "\n"; + + delete mem; + } + }; + + /** + * The Caretaker doesn't depend on the Concrete Memento class. Therefore, it + * doesn't have access to the originator's state, stored inside the memento. It + * works with all mementos via the base Memento interface. + */ + class CareTaker + { + private: + std::vector m_mementos; + Originator *m_originator; + + public: + explicit CareTaker(Originator *const org) : m_originator{org} {} + ~CareTaker() + { + for (IMemento *m : m_mementos) + { + delete m; + } + } + + void backup() + { + std::cout << "[C]Saving Originator's state...\n"; + this->m_mementos.push_back(this->m_originator->save()); + } + + void undo() + { + if (this->m_mementos.size() != 0) + { + IMemento *mem = m_mementos.back(); + this->m_mementos.pop_back(); + + std::cout << "[C]Restoring state to: " << mem->getName() << "\n"; + this->m_originator->restore(mem); + } + } + + void history() const + { + std::cout << "[C]The list of mementos:\n"; + for (const IMemento *m : m_mementos) + { + std::cout << "\t" << m->getName() << "\n"; + } + } + }; + + namespace Client + { + void clientCode(Originator *const org) + { + CareTaker *careTaker = new CareTaker(org); + + // 1 + careTaker->backup(); + org->operation(); + + // 2 + careTaker->backup(); + org->operation(); + + // 3 + careTaker->backup(); + org->operation(); + + careTaker->history(); + careTaker->undo(); + careTaker->undo(); + careTaker->history(); + careTaker->undo(); + + // [P] The previous state can’t be restored directly because m_state is private. + // We need a Memento to save and recover internal state safely. + } + } + + void run() + { + // Gen seed + std::srand(static_cast(std::time(NULL))); + + Originator *origin = new Originator("Hello World"); + Client::clientCode(origin); + + delete origin; + } + } +} + +struct MementoAutoRunner +{ + MementoAutoRunner() + { + std::cout << "\n--- Memento Pattern Example ---\n"; + Memento::run(); + } +}; + +static MementoAutoRunner instance; \ No newline at end of file