Снимок — это поведенческий паттерн, позволяющий делать снимки внутреннего состояния объектов, а затем восстанавливать их.
При этом Снимок не раскрывает подробностей реализации объектов, и клиент не имеет доступа к защищённой информации объекта.
Концептуальный пример
Этот пример показывает структуру паттерна Снимок , а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире PHP.
index.php: Пример структуры паттерна
<?php
namespace RefactoringGuru\Memento\Conceptual;
/**
* Создатель содержит некоторое важное состояние, которое может со временем
* меняться. Он также объявляет метод сохранения состояния внутри снимка и метод
* восстановления состояния из него.
*/
class Originator
{
/**
* @var string Для удобства состояние создателя хранится внутри одной
* переменной.
*/
private $state;
public function __construct(string $state)
{
$this->state = $state;
echo "Originator: My initial state is: {$this->state}\n";
}
/**
* Бизнес-логика Создателя может повлиять на его внутреннее состояние.
* Поэтому клиент должен выполнить резервное копирование состояния с помощью
* метода save перед запуском методов бизнес-логики.
*/
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,
);
}
/**
* Сохраняет текущее состояние внутри снимка.
*/
public function save(): Memento
{
return new ConcreteMemento($this->state);
}
/**
* Восстанавливает состояние Создателя из объекта снимка.
*/
public function restore(Memento $memento): void
{
$this->state = $memento->getState();
echo "Originator: My state has changed to: {$this->state}\n";
}
}
/**
* Интерфейс Снимка предоставляет способ извлечения метаданных снимка, таких как
* дата создания или название. Однако он не раскрывает состояние Создателя.
*/
interface Memento
{
public function getName(): string;
public function getDate(): string;
}
/**
* Конкретный снимок содержит инфраструктуру для хранения состояния Создателя.
*/
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');
}
/**
* Создатель использует этот метод, когда восстанавливает своё состояние.
*/
public function getState(): string
{
return $this->state;
}
/**
* Остальные методы используются Опекуном для отображения метаданных.
*/
public function getName(): string
{
return $this->date . " / (" . substr($this->state, 0, 9) . "...)";
}
public function getDate(): string
{
return $this->date;
}
}
/**
* Опекун не зависит от класса Конкретного Снимка. Таким образом, он не имеет
* доступа к состоянию создателя, хранящемуся внутри снимка. Он работает со
* всеми снимками через базовый интерфейс Снимка.
*/
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";
}
}
}
/**
* Клиентский код.
*/
$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: Результат выполнения
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
Пример из реальной жизни
Из-за того, что скрипты на PHP всегда имеют только один поток и выполняются в пределах короткой сессии, реальное использование этого паттерна в PHP выглядит для меня туманным. Хороший и удачный пример из реальной жизни, к сожалению, мне ещё не встречался. Почти любую задачу, которую я могу вообразить, можно осуществить легче с помощью сериализации.
Если вы имеете идею для хорошего примера, пожалуйста, предложите её на нашем форуме или по email support@refactoring.guru . Спасибо!