В данной заметке я опишу паттерн “Снимок” или “Memento”
Данный паттерн относится к “Поведенческим” шаблонам проектирования.
Допустим мы разрабатываем графический редактор, и нам нужно добавить возможность откатывать действия по команде пользователя. Также очень важно чтобы у компонентов системы не было доступа к внутреннему состоянию откатываемых “действий”, при реализации данного паттерна у других компонентов системы есть доступ только к обьекту-снимку без возможности менять его внутреннее состояние, с предоставлением понятного, простого внешнего интерфейса . Для решения данной задачи используется паттерн “Снимок” или “Хранитель”.
Пример работы “Снимка” представлен ниже:
При нажатии появляется спрайт, при нажатии на закрученную стрелку действие отменяется – спрайт исчезает. Пример состоит из трех классов:
- Канва (canvas) на котором отображаются спрайты, графический интерфейс.
- Контроллер экрана, он обрабатывает нажатия и управляет логикой экрана.
- Состояния канвы которые сохраняются при каждом изменении, откатываются при необходимости с помощью контроллера экрана.
В разрезе паттерна “Снимок” классы представляют из себя:
- Канва – источник, состояния этого класса сохраняются как “снимки”, для последующего отката по запросу. Также источник обязан уметь восстанавливать состояние при передаче ему “снимка”.
- Контроллер – хранитель, этот класс знает как и когда сохранять/откатывать состояния.
- Состояние – снимок, класс который хранит состояние источника, плюс информацию о дате или индекс, по которому можно точно установить порядок для отката.
Важная особенность паттерна состоит в том, что иметь доступ к внутренним полям сохраненного состояния в снимке должен только источник, это необходимо для защиты снимков от изменений из вне (от рукастых разработчиков, желающих что-то поменять в обход инкапсуляции, ломающих логику системы). Для реализации инкапсуляции используют встраиваемые классы, а в C++ применяют возможность задания friend классов. Лично я реализовал простую версию без инкапсуляции для Rise, и с использованием Generic при реализации для Swift. В моем варианте – Memento отдает свой внутренний стейт только сущностям одного со стейтом класса:
Источники
https://refactoring.guru/design-patterns/memento
Исходный код
https://gitlab.com/demensdeum/patterns/