Patrón Memento
Según el libro "Design Patterns: Elements of Reusable Object-Oriented Software" de GoF, el patrón Memento se define como: capturar el estado interno de un objeto sin violar su encapsulamiento, y almacenarlo fuera del objeto para poder restaurarlo posteriormente a ese estado.
A veces es necesario conservar el estado interno de un objeto. Para permitir operaciones deshacer o recuperación de errores, es necesario implementar puntos de control y mecanismos de reversión. Para lograr esto, debes guardar la información del estado en algún lugar para poder restaurar el objeto a su estado previo. ¿Cómo hacer esto? Usando el patrón prototipo podría parecer una solución, pero dado que los objetos encapsulan parte o toda su información de estado, no pueden ser accedidos desde fuera, lo cual impide almacenar sus estados externamente. Además, el patrón prototipo devuelve siempre todo el estado del objeto y permite acceso externo, rompiendo así el principio de encapsulamiento y potencialmente afectando la confiabilidad y extensibilidad de la aplicación.
Consideremos el juego "Xian Jian Qi Xia Zhuan" (Chinese Paladin). Antes de enfrentar al jefe final, guardamos el estado actual del juego incluyendo escena, misiones y personajes. Si ganamos, reemplazamos el archivo guardado anterior con uno nuevo. Si perdemos, cargamos el estado guardado previamente para reiniciar el combate contra el jefe. Este comportamiento utiliza el patrón Memento.
Un memento es un objeto que almacena el estado interno de otro objeto, llamado emisor. Cuando se requiere establecer un punto de verificación, el mecanismo de deshacer solicita al emisor un memento. El emisor inicializa este memento con la información del estado actual. Solo el emisor puede acceder a los datos del memento, siendo invisible para otros objetos.
Diagrama UML
Memento: Almacena el estado interno del objeto emisor. El emisor decide qué partes del estado debe almacenar; evita que objetos externos accedan al contenido del memento. Tiene dos interfaces: una interfaz estrecha que solo permite pasar el memento a otros objetos, y una interfaz ancha que permite al emisor acceder a todos los datos necesarios para restaurar el estado anterior. Idealmente, solo el emisor que crea el memento debería tener acceso a su contenido interno.
Originator: Crea un memento para registrar su estado actual; usa el memento para restaurar dicho estado.
Caretaker: Almacnea los mementos, pero no puede examinar ni modificar su contenido.
El patrón colabora de la siguiente manera: el cuidador solicita un memento al emisor, lo guarda por un tiempo y luego se lo devuelve; a veces, el cuidador no devuelve el memento al emisor porque podría no necesitar restaurar el estado anterior. El memento es pasivo: solo el emisor que lo creó puede leer o escribir en él, tal como se muestra en el diagrama de secuencia.
Casos de uso
Utiliza el patrón Memento cuando:
- Necesitas guardar parte o todo el estado de un objeto en un momento específico para restaurarlo más tarde.
- Si exponer el estado mediante una interfaz revelaría detalles de implementación y rompería el encapsulamiento del objeto.
Implementación en código
1 #include <iostream>
2 using namespace std;
3
4 struct GameState
5 {
6 wchar_t wcsState[260];
7 };
8
9 class Memento
10 {
11 public:
12 Memento(GameState *pState) : m_pState(pState){}
13
14 GameState *GetState() { return m_pState; }
15
16 private:
17 friend class Originator;
18
19 GameState *m_pState;
20 };
21
22 class Originator
23 {
24 public:
25 Originator() : m_pState(NULL) {}
26 ~Originator()
27 {
28 // Liberar el almacenamiento del estado
29 if (m_pState)
30 {
31 delete m_pState;
32 m_pState = NULL;
33 }
34 }
35
36 void SetMemento(Memento *pMemento);
37 Memento *CreateMemento();
38
39 void SetValue(wchar_t *value)
40 {
41 memset(wcsValue, 0, 260 * sizeof(wchar_t));
42 wcscpy_s(wcsValue, 260, value);
43 }
44
45 void PrintState() { wcout<<wcsValue<<endl; }
46
47 private:
48 GameState *m_pState; // Para almacenar el estado del objeto
49 wchar_t wcsValue[260]; // Datos reales del objeto
51 };
52
53 Memento *Originator::CreateMemento()
54 {
55 m_pState = new GameState;
56 if (m_pState == NULL)
57 {
58 return NULL;
59 }
60 Memento *pMemento = new Memento(m_pState);
61 wcscpy_s(m_pState->wcsState, 260, wcsValue); // Guardar el valor
62 return pMemento;
63 }
64
65 void Originator::SetMemento(Memento *pMemento)
66 {
67 m_pState = pMemento->GetState();
68 // Recuperar los datos
69 memset(wcsValue, 0, 260 * sizeof(wchar_t));
70 wcscpy_s(wcsValue, 260, m_pState->wcsState);
71 }
72
73 // Administrar el Memento
74 class Caretaker
75 {
76 public:
77 Memento *GetMemento() { return m_pMemento; }
78 void SetMemento(Memento *pMemento)
79 {
80 // Liberar el Memento anterior
81 if (m_pMemento)
82 {
83 delete m_pMemento;
84 m_pMemento = NULL;
85 }
86 // Establecer el nuevo Memento
87 m_pMemento = pMemento;
88 }
89 private:
90 Memento *m_pMemento;
91 };
92
93 int main()
94 {
95 Originator *pOriginator = new Originator();
96 pOriginator->SetValue(L"On");
97 pOriginator->PrintState();
98 // Ahora hago una copia de seguridad del estado
99 Caretaker *pCaretaker = new Caretaker();
100 pCaretaker->SetMemento(pOriginator->CreateMemento());
101 // Establecer un nuevo estado
102 pOriginator->SetValue(L"Off");
103 pOriginator->PrintState();
104 // Restaurar al estado anterior
105 pOriginator->SetMemento(pCaretaker->GetMemento());
106 pOriginator->PrintState();
107 if (pCaretaker)
108 {
109 delete pCaretaker;
110 }
111 if (pOriginator)
112 {
113 delete pOriginator;
114 }
115 return 0;
116 }
Aplicando el ejemplo del juego mencionado anteriormente, aquí está el código completo del patrón Memento:
1 #include <iostream>
2 using namespace std;
3
4 class RoleStateMemento
5 {
6 public:
7 RoleStateMemento(unsigned iHealth, unsigned iStrength, unsigned iDefense) : m_iHealth(iHealth), m_iStrength(iStrength), m_iDefense(iDefense){}
8
9 private:
10 friend class GameCharacter;
11 unsigned GetHealthValue() { return m_iHealth; }
12 unsigned GetStrengthValue() { return m_iStrength; }
13 unsigned GetDefenseValue() { return m_iDefense; }
14 unsigned m_iHealth; // Salud
15 unsigned m_iStrength; // Fuerza
16 unsigned m_iDefense; // Defensa
17 };
18
19 class GameCharacter
20 {
21 public:
22 GameCharacter() : m_iHealth(100), m_iStrength(100), m_iDefense(100){}
23 // Guardar estado
24 RoleStateMemento *SaveState() { return new RoleStateMemento(m_iHealth, m_iStrength, m_iDefense); }
25 // Recuperar estado guardado
26 void RestoreState(RoleStateMemento *pRoleState)
27 {
28 m_iHealth = pRoleState->GetHealthValue();
29 m_iStrength = pRoleState->GetStrengthValue();
30 m_iDefense = pRoleState->GetDefenseValue();
31 cout<<"Restoring..."<<endl;
32 }
33 void DisplayState()
34 {
35 cout<<"Health:"<<m_iHealth<<endl;
36 cout<<"Strength:"<<m_iStrength<<endl;
37 cout<<"Defense:"<<m_iDefense<<endl;
38 }
39 void Battle()
40 {
41 m_iHealth -= 100;
42 m_iStrength -= 10;
43 m_iDefense -= 20;
44 if (m_iHealth == 0)
45 {
46 cout<<"Game Over"<<endl;
47 }
48 }
49 private:
50 unsigned m_iHealth; // Salud
51 unsigned m_iStrength; // Fuerza
52 unsigned m_iDefense; // Defensa
53 };
54 class RoleStateKeeper
55 {
56 public:
57 void SetRoleStateMemento(RoleStateMemento *pRoleStateMemento) { m_pRoleStateMemento = pRoleStateMemento; }
58 RoleStateMemento *GetRoleStateMemento() { return m_pRoleStateMemento; }
59 private:
60 RoleStateMemento *m_pRoleStateMemento;
61 };
62 int main()
63 {
64 GameCharacter *pLiXY = new GameCharacter(); // Crear el personaje Li Xiao Yao
65 pLiXY->DisplayState(); // Mostrar estado inicial
66 // Hacer una copia de seguridad
67 RoleStateKeeper *pRoleStateKeeper = new RoleStateKeeper();
68 pRoleStateKeeper->SetRoleStateMemento(pLiXY->SaveState());
69 // Iniciar combate contra el jefe final
70 pLiXY->Battle();
71 pLiXY->DisplayState();
72 // Cargar estado anterior para reintentar
73 pLiXY->RestoreState(pRoleStateKeeper->GetRoleStateMemento());
74 pLiXY->DisplayState();
75 return 0;
76 }
Resumen
El patrón Memanto tiene múltiples aplicaciones prácticas; por ejemplo, en editores de texto, la función de deshacer. La clave en implementar este patrón en C++ es que la clase Originator debe declararse como amiga de la clase Memento, garantizando que solo el emisor pueda manipular el estado del memento. Dado que este patrón se utiliza principalmente para respaldar estados de objetos, permitiendo operaciones de deshacer, puede consumir muchos recursos si los datos del estado son voluminosos. En el desarrollo real, esto debe considerarse cuidadosamente. En combinación con otros patrones de diseño, como el patrón comando, que soporta deshacer transacciones, este patrón Memento es fundamental para su implementación. Así termina esta explicación sobre el patrón Memento. Esperamos que sea útil.