Паттерн “Снимок”

В данной заметке я опишу паттерн “Снимок” или “Memento”

Данный паттерн относится к “Поведенческим” шаблонам проектирования.

Допустим мы разрабатываем графический редактор, и нам нужно добавить возможность откатывать действия по команде пользователя. Также очень важно чтобы у компонентов системы не было доступа к внутреннему состоянию откатываемых “действий”, при реализации данного паттерна у других компонентов системы есть доступ только к обьекту-снимку без возможности менять его внутреннее состояние, с предоставлением понятного, простого внешнего интерфейса . Для решения данной задачи используется паттерн “Снимок” или “Хранитель”.

Пример работы “Снимка” представлен ниже:

При нажатии появляется спрайт, при нажатии на закрученную стрелку действие отменяется – спрайт исчезает. Пример состоит из трех классов:

  1. Канва (canvas) на котором отображаются спрайты, графический интерфейс.
  2. Контроллер экрана, он обрабатывает нажатия и управляет логикой экрана.
  3. Состояния канвы которые сохраняются при каждом изменении, откатываются при необходимости с помощью контроллера экрана.

В разрезе паттерна “Снимок” классы представляют из себя:

  1. Канва – источник, состояния этого класса сохраняются как “снимки”, для последующего отката по запросу. Также источник обязан уметь восстанавливать состояние при передаче ему “снимка”.
  2. Контроллер – хранитель, этот класс знает как и когда сохранять/откатывать состояния.
  3. Состояние – снимок, класс который хранит состояние источника, плюс информацию о дате или индекс, по которому можно точно установить порядок для отката.

Важная особенность паттерна состоит в том, что иметь доступ к внутренним полям сохраненного состояния в снимке должен только источник, это необходимо для защиты снимков от изменений из вне (от рукастых разработчиков, желающих что-то поменять в обход инкапсуляции, ломающих логику системы). Для реализации инкапсуляции используют встраиваемые классы, а в C++ применяют возможность задания friend классов. Лично я реализовал простую версию без инкапсуляции для Rise, и с использованием Generic при реализации для Swift. В моем варианте – Memento отдает свой внутренний стейт только сущностям одного со стейтом класса:

Источники

https://refactoring.guru/design-patterns/memento

Исходный код

https://gitlab.com/demensdeum/patterns/