НЕ МОЛЧИ!!!    Сделай что-нибудь,
чтобы остановить войну России в Украине.
...бойтесь людей равнодушных - именно с их молчаливого согласия происходят все самые ужасные преступления на свете.   ("Репортаж с петлёй на шее")

Шаблон проектирования Memento(Хранитель)

Рассмотрен поведенческий шаблон проектирования Memento, его назначение, условия применения. Приведен пример реализации шаблона.

Содержание

  • Содержание
  • Применение
  • Итоги

Применение

Шаблон проектирования Memento(Хранитель) используется в случаях, когда необходими сохранить состояние объекта с возможностью последующего восстановления. Особенность шаблона в том, что сохраняемый объект не обязан иметь полную функциональность для отката состояния. Конечно, можно сохранить состояние объекта считав напрямую значения его свойств. Однако преимущество шаблона Memento в том, что при сохранении снимка объекта знание деталей его реализации клиенту не требуется. Это обеспечивает простоту модернизации кода объекта, ведь в случае изменения состава или формата его свойств, нам не придется править сторонний код других компонентов ПО, которые напрямую взаимодействуют с объектом.

Реализация

Перед описанием реализации договоримся о терминах. Кроме прочего, это поможет объяснить идею шаблона. В нашем примере будут использоваться Originator(Создатель), Caretaker(Смотритель) и Memento(Хранитель).

  • Originator — Создатель — объект, состояния которого запоминаются и хранятся. По командам создает снимки своего состояния и восстанавливает своё состояние по этим снимкам;
  • Caretaker — Смотритель — дает команды Создателю на создание и восстановление снимков. Восстановление значения выполняется по принципу LIFO;
  • Memento — Хранитель — объект, хранящий состояние Создателя.

Думаю из описания уже просматривается принцип работы шаблона Memento. Реализован он следующим образом. По команде Смотрителя Создатель создает снимок своих свойств, который запоминается в объекте Хранитель. Если теперь после изменения состояния Создателя, понадобится откатить изменения, Смотритель дает команду Создателю на восстановление. По этой команде Создатель восстанавливает своё предыдущее состояние с помощью объекта Хранитель, передаваемого в команде восстановления.
Вот теперь можно привести код. Начнем с Memento. Его обязанности скромны — сохранить или вернуть переменную.

class Memento
{
    private int $state;

    public function __construct(int $state)
    {
        $this->state = $state;
    }

    public function getState(): int
    {
        return $this->state;
    }
}

Для простоты кода класс создателя Originator будет иметь лишь одно свойство, состояние которого и будет запоминаться.

class Originator
{
    private int $price;

    public function setPrice(int $price): void
    {
        $this->price = $price;
        echo "Price set to: {$this->price}" . PHP_EOL;
    }

    public function getPrice(): int
    {
        return $this->price;
    }

    public function save(): Memento
    {
        return new Memento($this->price);
    }

    public function restore(Memento $memento): void
    {
        $this->price = $memento->getState();
        echo "State restored: {$this->price}" . PHP_EOL;
    }
}

Как приводилось выше класс Caretaker управляет состоянием Создателя. Cвойство $state содержит массив состояний.

class Caretaker
{
    private array $history = [];

    public function push(Memento $memento): void
    {
        $this->history[] = $memento;
    }

    public function pop(): ?Memento
    {
        return array_pop($this->history);
    }

    public function count(): int
    {
        return count($this->history);
    }
}
template Memento
UML диаграмма классов шаблона Memento

Осталось продемонстрировать, как все это взаимодействует.

$originator = new Originator();
$caretaker = new Caretaker();

$originator->setPrice(10);
$caretaker->push($originator->save());

$originator->setPrice(20);
$caretaker->push($originator->save());

$originator->setPrice(30);

echo "Current price: " . $originator->getPrice() . PHP_EOL;

// undo
$memento = $caretaker->pop();
$originator->restore($memento);

echo "After undo: " . $originator->getPrice() . PHP_EOL;

Вышеприведенный код работы шаблона Memento выведет следующее.

Результат:

Price set to: 10
Price set to: 20
Price set to: 30
Current price: 30
State restored: 20
After undo: 20

Итоги

Главный итог — использование шаблона проектирования Memento позволяет сохранять и восстанавливать состояние объекта наблюдения без знания деталей его внутренней реализации. Это достигается за счет того, что объект наблюдения сам формирует снимок своего состояния по команде извне.
Применение шаблона Memento облегчает доработку программного обеспечения за счет уменьшения взаимосвязей компонентов ПО, позволяет организовать откат изменений объекта без знания его внутренней структуры.

Перейти к списку шаблонов проектирования.