В начале своего разработческого пути, меня часто посещало желание понять, осознать и принять паттерны. Но в интернете, в подавляющем большинстве случаев, этот материал крайне сложен для начинающих программистов. Поэтому я и решил создать цикл статей, в которых разберу все GoF паттерны (если будет интересно, в следующей статье могу кратко рассказать про историю GoF шаблонов и паттернов в целом).

Первый паттерн, который я бы хотел с вами разобрать - Memento (снимок). После, с перерывом ~2 недели будут выходить и остальные. Если у вас есть замечания или вы что-то не понимаете - пишите в комменты, разбор самых интересных вопросов я добавлю в статью.

Ну хватит вводной части, давайте разберём Снимок (ссылка на код внизу статьи).


Снимок — это поведенческий паттерн проектирования, который позволяет сохранять и восстанавливать прошлые состояния объектов, не раскрывая подробностей их реализации. Что из этого мы можем вынести? Снимок - шаблон, который позволяет сохранять состояние (т.е. данные) объекта и восстанавливать это состояние при необходимости. Самый простой пример - комбинация Ctrl+Z. При любом изменении, предположим, текстового файла, компьютер сохраняет состояние, которое было до изменения. При нажатии Ctrl+Z он восстанавливает это состояние. Давайте теперь перейдём к проблеме: В случае создания обычного класса, в поля которого мы будем записывать текущее состояние, появится уйма проблем. Например:

  1. При добавлении нового поля придётся переделывать значительную часть кода;

  2. Класс может изменить состояние;

  3. Мы не можем сделать поля приватными, т.к. тогда пользователь не сможет получить значение с этого поля когда это понадобится.

У вас могут возникнуть вопросы: 

-Окей, но есть ли решение и вообще, почему так происходит? 

Начнём со второго вопроса. Это происходит из-за нарушение инкапсуляции. Так что же нам предлагает паттерн? Паттерн Снимок поручает создание копии состояния объекта самому объекту, который этим состоянием владеет. Вместо того, чтобы делать снимок «извне», наш класс сам сделает копию своих полей, ведь ему доступны все поля, даже приватные. Шаблон предлагает держать копию состояния в специальном объекте-снимке с ограниченным интерфейсом, позволяющим, например, узнать дату изготовления или название снимка. Но, с другой стороны, снимок должен быть открыт для своего создателя, позволяя прочесть и восстановить его внутреннее состояние. Такая схема позволяет создателям производить снимки и отдавать их для хранения другим объектам, называемым опекунами. Снимок полностью открыт для создателя, но лишь частично открыт для опекунов. Опекунам будет доступен только ограниченный интерфейс снимка, поэтому они никак не смогут повлиять на «внутренности» самого снимка.

В нужный момент опекун может попросить создателя восстановить своё состояние, передав ему соответствующий снимок. И очень важное уточнение, восстановление значения выполняется по принципу LIFO (last in first out, последним пришел — первым ушел). Ну хватит теории, давайте напишем Снимок на Python (Полный код шаблона внизу статьи). Я буду писать пример сохранения информации о кол-ве вакансий на Python разработчика. Начнём, конечно, с абстрактных классов.

Это класс вакансии. У него есть два интерфейса для взаимодействия со Снимком - save и restore (Сохранить и восстановить состояние).

Абстрактный класс вакансий
Абстрактный класс вакансий

Теперь класс, вроде банковской ячейки где и будет хранится наше состояние.

Класс хранящий состояние
Класс хранящий состояние

Данный класс будет отвечать за хранение списка всех состояний объекта. 

Класс хранящий список состояний
Класс хранящий список состояний

Итак, сейчас начинается самое интересное - сам код! Начнём с конца. Напишем код для сохранения списка с кол-вом Python вакансий. Объясню: в __init__ мы создаём два приватных поля, которые будут отвечать за сохранение и восстановление предыдущего состояния. В save_state мы будем сохранять состояние, с помощью добавления объекта MementoVacancy в список, get_previous_state откатывает состояние до предыдущего, удаляя при этом наше текущее состояние. И show_history, тут мы будем видеть список всех наших состояний в виде списка. 

Добавьте описание
Добавьте описание

Тут у нас класс MementoPythonVacancy. Здесь всё очень просто, думаю объяснять не стоит. 

Добавьте описание
Добавьте описание

Это класс Python вакансий. Код тоже очень простой. 

Добавьте описание
Добавьте описание

А теперь давайте посмотрим, как всё-таки это работает)Сначала создаём объект класса Python вакансий (переменная feb), сейчас их по умолчанию 17000. Потом меняем их на 15000 и 16000, после чего создаём наше хранилище состояний (переменная python_saver) и сохраняем текущее состояние (т.е. 16000). Дальше устанавливаем 13000 вакансий и сохраняем, ставим 12000 вакансий и также сохраняем. Теперь очередь принта, выводим всю историю состояний, потом откатываемся на 2 состояния назад и выводим кол-во вакансий. 

Пример использования созданных классов
Пример использования созданных классов

Вот что получится при запуске: 

Результат работы скрипта
Результат работы скрипта

Вот и весь шаблон, думаю вам вполне понятно где используется Снимок. Это программы, в которых мы должны иметь возможность сохранять состояние, откатываться назад и видеть всю историю изменений. Не так уж и сложно, верно? :)


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

Как и обещал, ссылка на код (Да это ЯД, не хочется заливать на гит): https://disk.yandex.ru/d/Swb1WULZT1jNhw

Мой GitHub

Комментарии (7)


  1. MentalBlood
    24.09.2022 13:37
    +2

    Самый простой пример — комбинация Ctrl+Z. При любом изменение предположим текстового файла, компьютер сохраняет состояние которое было до изменения. При нажатии Ctrl+Z он восстанавливает это состояние

    Скорее всего сохраняется не состояние, а его изменение (дельта). Так меньше нагрузка на память


    1. me21
      24.09.2022 19:19
      +1

      Это детали реализации.


  1. domix32
    24.09.2022 15:57
    +6

    Да это ЯД,

    bruh. Рекламировать собственный гитхаб, но лить на яд? Лучше сделай код как вот у этого чувака, чтобы можно было нормально склонировать и запустить отдельные куски кода, а не вот это вот всё.


  1. BSergius
    25.09.2022 00:11
    +6

    Видя ссылку на яндекс диск, сразу руки опускаются и пропадает желание читать. Возникает мысль: как у разработчика в здравом уме вообще возникла такая мысель выложить код на диск?


  1. hint000
    25.09.2022 16:45
    +5

    1. Зачем вообще вставлять код скриншотами, а не кодом?

    2. На скриншотах проверка орфографии подчёркнула ошибки? Пренебречь, вальсируем.


  1. BasicWolf
    25.09.2022 21:36

    def set_vacancy_count(count):
        if abs(count) == count:
            self._vacancy_count = count

    Ну зачем такое зло? Я понимаю, что это пример, но даже в примере такое писать нельзя. Это же чистой воды "неочевидное поведение". Почему операция сеттера молча проглатывает неправильные значения?


    1. Metotron0
      26.09.2022 04:55

      А в питоне что ли это работает быстрее, чем if count >= 0? Зачем автор так написал?