В начале своего разработческого пути, меня часто посещало желание понять, осознать и принять паттерны. Но в интернете, в подавляющем большинстве случаев, этот материал крайне сложен для начинающих программистов. Поэтому я и решил создать цикл статей, в которых разберу все GoF паттерны (если будет интересно, в следующей статье могу кратко рассказать про историю GoF шаблонов и паттернов в целом).
Первый паттерн, который я бы хотел с вами разобрать - Memento (снимок). После, с перерывом ~2 недели будут выходить и остальные. Если у вас есть замечания или вы что-то не понимаете - пишите в комменты, разбор самых интересных вопросов я добавлю в статью.
Ну хватит вводной части, давайте разберём Снимок (ссылка на код внизу статьи).
Снимок — это поведенческий паттерн проектирования, который позволяет сохранять и восстанавливать прошлые состояния объектов, не раскрывая подробностей их реализации. Что из этого мы можем вынести? Снимок - шаблон, который позволяет сохранять состояние (т.е. данные) объекта и восстанавливать это состояние при необходимости. Самый простой пример - комбинация Ctrl+Z. При любом изменении, предположим, текстового файла, компьютер сохраняет состояние, которое было до изменения. При нажатии Ctrl+Z он восстанавливает это состояние. Давайте теперь перейдём к проблеме: В случае создания обычного класса, в поля которого мы будем записывать текущее состояние, появится уйма проблем. Например:
При добавлении нового поля придётся переделывать значительную часть кода;
Класс может изменить состояние;
Мы не можем сделать поля приватными, т.к. тогда пользователь не сможет получить значение с этого поля когда это понадобится.
У вас могут возникнуть вопросы:
-Окей, но есть ли решение и вообще, почему так происходит?
Начнём со второго вопроса. Это происходит из-за нарушение инкапсуляции. Так что же нам предлагает паттерн? Паттерн Снимок поручает создание копии состояния объекта самому объекту, который этим состоянием владеет. Вместо того, чтобы делать снимок «извне», наш класс сам сделает копию своих полей, ведь ему доступны все поля, даже приватные. Шаблон предлагает держать копию состояния в специальном объекте-снимке с ограниченным интерфейсом, позволяющим, например, узнать дату изготовления или название снимка. Но, с другой стороны, снимок должен быть открыт для своего создателя, позволяя прочесть и восстановить его внутреннее состояние. Такая схема позволяет создателям производить снимки и отдавать их для хранения другим объектам, называемым опекунами. Снимок полностью открыт для создателя, но лишь частично открыт для опекунов. Опекунам будет доступен только ограниченный интерфейс снимка, поэтому они никак не смогут повлиять на «внутренности» самого снимка.
В нужный момент опекун может попросить создателя восстановить своё состояние, передав ему соответствующий снимок. И очень важное уточнение, восстановление значения выполняется по принципу 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
Комментарии (7)
BSergius
25.09.2022 00:11+6Видя ссылку на яндекс диск, сразу руки опускаются и пропадает желание читать. Возникает мысль: как у разработчика в здравом уме вообще возникла такая мысель выложить код на диск?
hint000
25.09.2022 16:45+5Зачем вообще вставлять код скриншотами, а не кодом?
На скриншотах проверка орфографии подчёркнула ошибки? Пренебречь, вальсируем.
BasicWolf
25.09.2022 21:36def set_vacancy_count(count): if abs(count) == count: self._vacancy_count = count
Ну зачем такое зло? Я понимаю, что это пример, но даже в примере такое писать нельзя. Это же чистой воды "неочевидное поведение". Почему операция сеттера молча проглатывает неправильные значения?
Metotron0
26.09.2022 04:55А в питоне что ли это работает быстрее, чем if count >= 0? Зачем автор так написал?
MentalBlood
Скорее всего сохраняется не состояние, а его изменение (дельта). Так меньше нагрузка на память
me21
Это детали реализации.