Memento (Снимок, Хранитель)¶
Категория: паттерн поведения.
Проблема¶
Нужно сохранять состояния объекта для undo, отката транзакции или контрольной точки. Если история сама читает и записывает поля объекта, приватное устройство раскрывается наружу, а любое изменение этих полей заставляет переделывать код истории.
Решение¶
Сам объект-создатель (Originator) формирует неизменяемый снимок своего закрытого состояния и умеет восстановиться из него. Опекун (Caretaker) решает, когда сохранить или вернуть состояние, но видит только ограниченный контракт снимка и не может менять его содержимое.
Структура¶
Originator- создаёт снимки и восстанавливает собственное состояние.Memento- хранит состояние создателя; обычно неизменяем.Caretaker- хранит историю снимков и управляет undo/redo, не заглядывая внутрь.
Варианты реализации¶
- Вложенный класс снимка - естественный вариант для C#: закрытый вложенный тип имеет доступ к устройству создателя, а опекун получает лишь публичный ограниченный интерфейс.
- Промежуточный интерфейс - создатель работает с конкретным снимком, опекун - с пустым или метаданным интерфейсом. Подходит, когда вложенный класс неудобен.
- Снимок с методом
Restore()- снимок сам знает, как восстановить связанный создатель. Это сильнее связывает их, но позволяет опекуну быть полностью независимым от типа создателя. - Полный или инкрементальный снимок - полный проще и надёжнее, инкрементальный хранит только изменения и экономит память ценой более сложного восстановления.
Когда применять¶
- Нужны undo/redo, история изменений, контрольные точки или откат неудачной операции.
- Прямое получение состояния нарушило бы инкапсуляцию объекта.
- Состояние достаточно компактно либо предусмотрены ограничения истории и инкрементальные снимки.
Плюсы¶
- Сохраняет инкапсуляцию: только создатель знает формат своего состояния.
- Ответственность за хранение истории отделена от бизнес-объекта.
- Упрощает отмену сложной операции, особенно вместе с Command.
Минусы¶
- Частые полные снимки больших графов потребляют много памяти.
- Опекун должен управлять временем жизни и очисткой истории.
- Снимок ссылочного графа требует осознанного выбора между поверхностной и глубокой копией.
Отличие от Command и Prototype¶
Command хранит действие и может реализовать undo обратной операцией. Memento хранит состояние до действия; вместе они дают надёжную отмену. Prototype создаёт новый полноценный объект-клон, а Memento создаёт закрытый контейнер состояния специально для последующего восстановления исходного объекта.
Пример в .NET¶
- Change tracking в ORM и история состояния редакторов используют близкие идеи, хотя их реализации не обязаны буквально повторять структуру GoF.
- Сериализация объекта может быть техническим способом получить снимок, но сама по себе сериализация не является паттерном Memento и часто слишком тяжела для частого undo.
Источник для сверки структуры и вариантов: Refactoring.Guru - Memento.