Ejemplo conceptual
Este ejemplo ilustra la estructura del patrón de diseño Memento y se centra en las siguientes preguntas:
¿De qué clases se compone?
¿Qué papeles juegan esas clases?
¿De qué forma se relacionan los elementos del patrón?
Después de conocer la estructura del patrón, será más fácil comprender el siguiente ejemplo basado en un caso de uso real de PHP.
index.php: Ejemplo conceptual
<?php
namespace RefactoringGuru\Memento\Conceptual;
/**
* The 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
{
/**
* @var string For the sake of simplicity, the originator's state is stored
* inside a single variable.
*/
private $state;
public function __construct(string $state)
{
$this->state = $state;
echo "Originator: My initial state is: {$this->state}\n";
}
/**
* The Originator's business logic may affect its internal state. Therefore,
* the client should backup the state before launching methods of the
* business logic via the save() method.
*/
public function doSomething(): void
{
echo "Originator: I'm doing something important.\n";
$this->state = $this->generateRandomString(30);
echo "Originator: and my state has changed to: {$this->state}\n";
}
private function generateRandomString(int $length = 10): string
{
return substr(
str_shuffle(
str_repeat(
$x = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
ceil($length / strlen($x))
)
),
1,
$length,
);
}
/**
* Saves the current state inside a memento.
*/
public function save(): Memento
{
return new ConcreteMemento($this->state);
}
/**
* Restores the Originator's state from a memento object.
*/
public function restore(Memento $memento): void
{
$this->state = $memento->getState();
echo "Originator: My state has changed to: {$this->state}\n";
}
}
/**
* The 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.
*/
interface Memento
{
public function getName(): string;
public function getDate(): string;
}
/**
* The Concrete Memento contains the infrastructure for storing the Originator's
* state.
*/
class ConcreteMemento implements Memento
{
private $state;
private $date;
public function __construct(string $state)
{
$this->state = $state;
$this->date = date('Y-m-d H:i:s');
}
/**
* The Originator uses this method when restoring its state.
*/
public function getState(): string
{
return $this->state;
}
/**
* The rest of the methods are used by the Caretaker to display metadata.
*/
public function getName(): string
{
return $this->date . " / (" . substr($this->state, 0, 9) . "...)";
}
public function getDate(): string
{
return $this->date;
}
}
/**
* 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
{
/**
* @var Memento[]
*/
private $mementos = [];
/**
* @var Originator
*/
private $originator;
public function __construct(Originator $originator)
{
$this->originator = $originator;
}
public function backup(): void
{
echo "\nCaretaker: Saving Originator's state...\n";
$this->mementos[] = $this->originator->save();
}
public function undo(): void
{
if (!count($this->mementos)) {
return;
}
$memento = array_pop($this->mementos);
echo "Caretaker: Restoring state to: " . $memento->getName() . "\n";
try {
$this->originator->restore($memento);
} catch (\Exception $e) {
$this->undo();
}
}
public function showHistory(): void
{
echo "Caretaker: Here's the list of mementos:\n";
foreach ($this->mementos as $memento) {
echo $memento->getName() . "\n";
}
}
}
/**
* Client code.
*/
$originator = new Originator("Super-duper-super-puper-super.");
$caretaker = new Caretaker($originator);
$caretaker->backup();
$originator->doSomething();
$caretaker->backup();
$originator->doSomething();
$caretaker->backup();
$originator->doSomething();
echo "\n";
$caretaker->showHistory();
echo "\nClient: Now, let's rollback!\n\n";
$caretaker->undo();
echo "\nClient: Once more!\n\n";
$caretaker->undo();
Output.txt: Resultado de la ejecución
Originator: My initial state is: Super-duper-super-puper-super.
Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: srGIngezAEboNPDjBkuvymJKUtMSFX
Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: UwCZQaHJOiERLlchyVuMbXNtpqTgWF
Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: incqsdoJXkbDUuVOvRFYyKBgfzwZCQ
Caretaker: Here's the list of mementos:
2018-06-04 14:50:39 / (Super-dup...)
2018-06-04 14:50:39 / (srGIngezA...)
2018-06-04 14:50:39 / (UwCZQaHJO...)
Client: Now, let's rollback!
Caretaker: Restoring state to: 2018-06-04 14:50:39 / (UwCZQaHJO...)
Originator: My state has changed to: UwCZQaHJOiERLlchyVuMbXNtpqTgWF
Client: Once more!
Caretaker: Restoring state to: 2018-06-04 14:50:39 / (srGIngezA...)
Originator: My state has changed to: srGIngezAEboNPDjBkuvymJKUtMSFX
Ejemplo del mundo real
Debido a que la mayoría de los scripts de PHP son de un único hilo y cuentan con un tiempo de sesión muy limitado, debes almacenar el estado de los objetos en un almacenamiento más permanente que la memoria RAM. Normalmente, es suficiente con tomar instantáneas a través de la serialización. Por lo tanto, no se me ocurren ideas para un ejemplo decente en el mundo real de este patrón en una aplicación PHP.
No obstante, si has utilizado el patrón en tu proyecto previamente, no dudes en compartir tu experiencia en el foro o por correo electrónico, en support@refactoring.guru . ¡Gracias!