Обзор шаблона проектирования Memento и его реализации в Dart и Flutter

В прошлой статье я проанализировал довольно популярный шаблон проектирования - Command. В этой статье я хотел бы проанализировать и реализовать шаблон поведенческого проектирования, который неплохо работает вместе с шаблоном Command - это Memento.

Оглавление

  • Что такое шаблон дизайна Memento?
  • Анализ
  • Реализация
  • Другие статьи из этой серии
  • Ваш вклад

Что такое шаблон дизайна Memento?

Memento, также известный как Token, относится к категории поведенческих шаблонов проектирования. Назначение этого паттерна проектирования описано в Книге GoF:

Не нарушая инкапсуляцию, фиксируйте и экстернализируйте внутреннее состояние объекта, чтобы впоследствии его можно было восстановить в этом состоянии.

Ключевая идея в этом шаблоне - сделать сам объект (создатель) ответственным за сохранение и восстановление его внутреннего состояния. Внутреннее состояние (его снимок) сохраняется в другом объекте - сувенире. Механизм отмены запросит памятный знак у отправителя, когда ему нужно восстановить внутреннее состояние отправителя. Клиенты (хранитель), которые несут ответственность за сохранение и восстановление внутреннего состояния отправителя, хранят список объектов-памяток, так что памятный знак может быть передан обратно отправителю для восстановления предыдущего состояния. Однако самому хранителю не разрешается получать доступ или изменять мементо - это разрешено делать только объекту-источнику, который создал конкретный мементо.

Чтобы лучше понять шаблон дизайна Memento, давайте углубимся в его структуру и более подробно рассмотрим реализацию!

Анализ

Общая структура паттерна Memento выглядит так:

  • Memento - интерфейс, который ограничивает доступ к полям ConcreteMemento, объявляет только методы, связанные с метаданными памятного предмета и которые используются смотрителем для работы с объектом ConcreteMemento;
  • ConcreteMemento - хранит внутреннее состояние отправителя. Кроме того, защищает от доступа других объектов, кроме Originator, создавшего ConcreteMemento.
  • Смотритель - отвечает за сохранность Memento и никогда не использует и не проверяет содержимое Memento.
  • Originator - создает ConcreteMemento, содержащий моментальный снимок его текущего внутреннего состояния. Кроме того, предоставляет метод restore () для восстановления внутреннего состояния с помощью ConcreteMemento.

Применимость

Шаблон проектирования Memento следует использовать, когда вы хотите создать снимки состояния объекта, чтобы иметь возможность восстановить предыдущее состояние объекта. Шаблон Memento позволяет создавать полные копии состояния объекта, включая частные поля, и сохранять их отдельно от объекта.

Кроме того, шаблон можно использовать из соображений безопасности - когда прямой доступ к полям / геттерам / сеттерам объекта нарушает его инкапсуляцию. Memento делает сам объект ответственным за создание снимка своего состояния. Никакой другой объект не может прочитать снимок, что делает данные о состоянии исходного объекта безопасными и надежными.

Реализация

Чтобы реализовать шаблон проектирования Memento и продемонстрировать его преимущества, мы продолжим работу на примере шаблона проектирования Command. Так что, если вы пропустили предыдущую статью, я настоятельно рекомендую проверить ее часть реализации сейчас.

Основная идея примера осталась прежней - мы создадим очень простой поддельный графический редактор. Чтобы упростить часть шаблона разработки Command, в пользовательском интерфейсе примера создается только одна команда - RandomisePropertiesCommand. Эта команда рандомизирует все свойства объекта Shape - высоту, ширину и цвет - что действует как состояние нашего примера.

Очевидно, чем отличается от предыдущей реализации - добавлен шаблон проектирования Memento. При реализации примера шаблона проектирования Command мы сохранили его состояние (объект Shape) в самом компоненте примера. На этот раз состояние хранится внутри объекта Originator и может управляться только им. RandomisePropertiesCommand действует как объект-хранитель и сохраняет предыдущий снимок состояния отправителя в свойстве backup. Свойство backup - это не что иное, как объект Memento, который создается создателем перед выполнением команды.

В результате использования шаблона проектирования Memento состояние примера инкапсулируется и перемещается за пределы компонента примера. Кроме того, предыдущее состояние можно было восстановить из его снимка Memento при выполнении команды undo (). В этом случае шаблон проектирования Memento расширяет шаблон проектирования Command и действительно хорошо с ним взаимодействует.

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

Диаграмма классов

На диаграмме классов ниже показана реализация шаблона проектирования Memento:

ICommand - это абстрактный класс, который используется в качестве интерфейса для конкретной команды:

  • execute () - абстрактный метод, выполняющий команду;
  • undo () - абстрактный метод, отменяющий команду и возвращающий состояние в ее предыдущий снимок.

RandomisePropertiesCommand - это конкретная команда, которая реализует абстрактный класс ICommand и его методы.

CommandHistory - это простой класс, который хранит список уже выполненных команд (commandList) и предоставляет методы для добавления новой команды в список истории команд (add () ) и отмените последнюю команду из этого списка (undo ()).

IMemento - это абстрактный класс, который используется в качестве интерфейса для определенного класса сувениров:

  • getState () - абстрактный метод, который возвращает снимок состояния внутреннего источника.

Memento - это класс, который действует как снимок внутреннего состояния создателя, который хранится в свойстве state и возвращается через метод getState (). .

Shape - это простой класс данных, который используется в качестве состояния внутреннего источника. Он хранит несколько свойств, определяющих форму, представленную в пользовательском интерфейсе: color, height и width.

Originator - простой класс, который содержит его внутреннее состояние и сохраняет его моментальный снимок в объекте Memento с помощью метода createMemento (). Кроме того, состояние отправителя можно восстановить из предоставленного объекта Memento с помощью метода restore ().

MementoExample инициализирует и содержит объекты CommandHistory, Originator. Кроме того, этот компонент содержит виджет PlatformButton, которому назначена команда RandomisePropertiesCommand. При нажатии кнопки команда выполняется и добавляется в список истории команд, хранящийся в объекте CommandHistory.

Форма

Простой класс для хранения информации о форме: ее цвет, высота и ширина. Также этот класс содержит несколько конструкторов:

  • Shape () - базовый конструктор для создания объекта формы с предоставленными значениями;
  • Shape.initial () - именованный конструктор для создания объекта формы с заранее определенными начальными значениями;
  • Shape.copy () - именованный конструктор для создания объекта формы как копии предоставленного значения Shape.

ICommand

Интерфейс, определяющий методы, которые будут реализованы конкретными классами команд. Язык Dart не поддерживает интерфейс как тип класса, поэтому мы определяем интерфейс, создавая абстрактный класс и предоставляя заголовок метода (имя, тип возвращаемого значения, параметры) без реализации по умолчанию.

RandomisePropertiesCommand

Конкретная реализация команды, которая устанавливает для всех свойств объекта Shape, хранящегося в Originator, случайные значения. Также класс реализует операцию отмены.

CommandHistory

Простой класс, в котором хранится список уже выполненных команд. Кроме того, этот класс предоставляет метод получения isEmpty для возврата значения true, если список истории команд пуст. Новую команду можно было добавить в список истории команд с помощью метода add (), а последнюю команду можно было бы отменить с помощью метода undo () (если список истории команд не пусто).

IMemento

Интерфейс, определяющий метод getState (), который должен быть реализован конкретным классом Memento.

Сувенир

Реализация интерфейса IMemento, в котором хранится моментальный снимок внутреннего состояния Originator (объект Shape). Состояние доступно для Originator через метод getState ().

Оригинатор

Класс, определяющий метод createMemento () для сохранения текущего внутреннего состояния в объект Memento.

Пример

Прежде всего, подготавливается файл разметки, который предоставляется как описание шаблона:

MementoExample содержит объекты CommandHistory и Originator. Кроме того, этот виджет содержит компонент PlatformButton, который использует RandomisePropertiesCommand для рандомизации значений свойств фигуры. После выполнения команды она добавляется в список истории команд, хранящийся в объекте CommandHistory. Если история команд не пуста, кнопка Отменить активна, и последняя команда может быть отменена.

Как вы можете видеть в этом примере, клиентский код (элементы пользовательского интерфейса, история команд и т. Д.) Не связан с каким-либо конкретным классом команд, потому что он работает с ним через интерфейс ICommand.

В дополнение к тому, что шаблон проектирования Command предоставляет этому примеру, шаблон проектирования Memento добавляет дополнительный уровень к состоянию примера. Он хранится внутри объекта Originator, сама команда изменяет состояние не напрямую, а через Originator. Кроме того, резервная копия (снимок состояния), хранящаяся внутри команды, является объектом Memento, а не самим состоянием (объектом Shape) - в случае восстановления состояния (отмена запускается по команде) конкретная команда вызывает метод восстановления для Создатель, который восстанавливает свое внутреннее состояние до значения, хранящегося в снимке. Следовательно, он позволяет восстанавливать несколько значений свойств (целый комплексный объект состояния) в одном запросе, в то время как само состояние полностью отделено от кода команды или логики пользовательского интерфейса.

Как вы можете видеть в примере, когда команда выполняется, под капотом сохраняется моментальный снимок внутреннего состояния создателя, который можно восстановить позже, выполнив операцию отмены для команды.

Все изменения кода для шаблона проектирования Memento и его пример реализации можно найти здесь.

Другие статьи из этой серии

Ваш вклад

👏 Нажмите кнопку хлопка ниже, чтобы выразить свою поддержку и побудить меня писать лучше!
💬 Оставьте отзыв на эту статью, поделившись своими мыслями, комментариями или пожеланиями относительно серии.
📢 Поделитесь этой статьей со своими друзья, коллеги в социальных сетях.
➕ Подписывайтесь на меня на Medium.
⭐ Пометьте репозиторий Github.