PHP: Снимок

Memento Снимок Memento

Снимок — это поведенческий паттерн, позволяющий делать снимки внутреннего состояния объектов, а затем восстанавливать их.

При этом Снимок не раскрывает подробностей реализации объектов, и клиент не имеет доступа к защищённой информации объекта.

Подробней о Снимке

Особенности паттерна в PHP

Сложность:

Популярность:

Применимость: Реальная применимость паттерна Снимок в PHP под большим вопросом. Чаще всего задачу хранения копии состояния можно решить куда проще при помощи сериализации.

Пример: Структура паттерна

Этот пример показывает структуру паттерна Снимок, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом. После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире PHP.

MementoStructural.php: Пример структуры паттерна

<?php

namespace RefactoringGuru\Memento\Structural;

/**
 * Создатель содержит некоторое важное состояние, которое может со временем
 * меняться. Он также объявляет метод сохранения состояния внутри снимка и метод
 * восстановления состояния из него.
 */
class Originator
{
    /**
     * @var mixed Для удобства состояние создателя хранится внутри одной
     * переменной.
     */
    private $state;

    public function __construct($state)
    {
        $this->state = $state;
        print("Originator: My initial state is: {$this->state}\n");
    }

    /**
     * Бизнес-логика Создателя может повлиять на его внутреннее состояние.
     * Поэтому клиент должен выполнить резервное копирование состояния с помощью
     * метода save перед запуском методов бизнес-логики.
     */
    public function doSomething()
    {
        print("Originator: I'm doing something important.\n");
        $this->state = $this->generateRandomString(30);
        print("Originator: and my state has changed to: {$this->state}\n");
    }

    private function generateRandomString($length = 10)
    {
        return substr(
            str_shuffle(
                str_repeat(
                    $x = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
                    ceil($length / strlen($x)))), 1, $length);
    }

    /**
     * Сохранияет текущее состояние внутри снимка.
     *
     * @return Memento
     */
    public function save(): Memento
    {
        return new ConcreteMemento($this->state);
    }

    /**
     * Восстанавливает состояние Создателя из объекта снимка.
     *
     * @param Memento $memento
     * @throws \Exception
     */
    public function restore(Memento $memento)
    {
        if (! $memento instanceof ConcreteMemento) {
            throw new \Exception("Unknown memento class ".get_class($memento));
        }

        $this->state = $memento->getState();
        print("Originator: My state has changed to: {$this->state}\n");
    }
}

/**
 * Интерфейс Снимка предоставляет способ извлечения метаданных снимка, таких как
 * дата создания или название. Однако он не раскрывает состояние Создателя.
 */
interface Memento
{
    public function getName();

    public function getDate();
}

/**
 * Конкретный снимок содержит инфраструктуру для хранения состояния Создателя.
 */
class ConcreteMemento implements Memento
{
    private $state;

    private $date;

    public function __construct($state)
    {
        $this->state = $state;
        $this->date = date('Y-m-d H:i:s');
    }

    /**
     * Создатель использует этот метод, когда восстанавливает своё состояние.
     */
    public function getState()
    {
        return $this->state;
    }

    /**
     * Остальные методы используются Опекуном для отображения метаданных.
     */
    public function getName()
    {
        return $this->date." / (".substr($this->state, 0, 9)."...)";
    }

    public function getDate()
    {
        return $this->date;
    }
}

/**
 * Опекун не зависит от класса Конкретного Снимка. Таким образом, он не имеет
 * доступа к состоянию создателя, хранящемуся внутри снимка. Он работает со
 * всеми снимками через базовый интерфейс Снимка.
 */
class Caretaker
{
    /**
     * @var Memento[]
     */
    private $mementos = [];

    /**
     * @var Originator
     */
    private $originator;

    public function __construct(Originator $originator)
    {
        $this->originator = $originator;
    }

    public function backup()
    {
        print("\nCaretaker: Saving Originator's state...\n");
        $this->mementos[] = $this->originator->save();
    }

    public function undo()
    {
        if (! count($this->mementos)) {
            return;
        }
        $memento = array_pop($this->mementos);

        print("Caretaker: Restoring state to: ".$memento->getName()."\n");
        try {
            $this->originator->restore($memento);
        } catch (\Exception $e) {
            $this->undo();
        }
    }

    public function showHistory()
    {
        print("Caretaker: Here's the list of mementos:\n");
        foreach ($this->mementos as $memento) {
            print($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();

print("\n");
$caretaker->showHistory();

print("\nClient: Now, let's rollback!\n\n");
$caretaker->undo();

print("\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. Спасибо!