Паттерн Хранитель в Python (Memento)
Паттерн Хранитель — это поведенческий паттерн проектирования, позволяющий захватывать и сохранять внутреннее состояние объекта, не нарушая инкапсуляцию, чтобы при необходимости можно было восстановить его до предыдущего состояния.
Представьте, что вы играете в игру, которая предоставляет функцию «сохранения». Вы можете в любой момент сохранить текущий игровой прогресс. Если позже вы потерпите неудачу или захотите переиграть уровень, вы можете загрузить предыдущее сохранение. Паттерн Хранитель — это программное решение, реализующее эту функциональность «сохранения-загрузки».
Зачем нам нужен паттерн Хранитель?
Сценарии применения
- Текстовый редактор: реализовать функции отмены и повтора
- Разработка игр: сохранение игрового прогресса и состояния
- Графическое ПО: поддержка истории операций
- Обработка транзакций: откат к предыдущему состоянию при сбое операции
Решаемые проблемы
- Необходимость сохранять состояние объекта, не раскрывая внутренние детали
- Предоставить механизм восстановления состояния, поддерживающий функцию «лекарства от сожалений» (то есть возможность отмены)
- Сохранять инкапсуляцию объекта и избегать смешивания логики сохранения состояния с бизнес-логикой
Основные компоненты паттерна Хранитель
Паттерн Хранитель состоит из трёх основных ролей:
1. Создатель
- Объект, которому необходимо сохранить состояние
- Создаёт снимок для фиксации текущего состояния
- Использует снимок для восстановления предыдущего состояния
2. Хранитель
- Хранит внутреннее состояние объекта-создателя
- Не позволяет объектам, кроме создателя, получать доступ к содержимому снимка
3. Опекун
- Отвечает за сохранение снимков
- Не может изменять или просматривать содержимое снимков

Реализация паттерна Хранитель на Python
Давайте конкретно реализуем паттерн Memento на примере текстового редактора.
Базовая реализация
Пример
"""Класс Memento — сохраняет состояние текстового редактора"""
def __init__(self, content):
self._content = content
def get_content(self):
"""Получить сохранённое содержимое"""
return self._content
class TextEditor:
"""Класс Originator — текстовый редактор"""
def __init__(self):
self._content = ""
def write(self, text):
"""Записать текст"""
self._content += text
def get_content(self):
"""Получить текущее содержимое"""
return self._content
def save(self):
"""Создать снимок - сохранить текущее состояние"""
return TextEditorMemento(self._content)
def restore(self, memento):
"""Восстановить состояние - прочитать из снимка"""
self._content = memento.get_content()
class HistoryManager:
"""Класс опекуна - менеджер истории"""
def __init__(self):
self._mementos = []
def push(self, memento):
"""Сохранить снимок"""
self._mementos.append(memento)
def pop(self):
"""Получить последний снимок"""
if self._mementos:
return self._mementos.pop()
return None
Полный пример кода
Пример
def memo_pattern_demo():
# Создать текстовый редактор и менеджер истории
editor = TextEditor()
history = HistoryManager()
print("=== Демонстрация паттерна Хранитель для текстового редактора ==="\n")
# Первое редактирование и сохранение
editor.write("Привет, ")
history.push(editor.save())
print(f"Текущее содержимое: {editor.get_content()}")
print("✅ Состояние сохранено\n")
# Второе редактирование и сохранение
editor.write("Мир!")
history.push(editor.save())
print(f"Текущее содержимое: {editor.get_content()}")
print("✅ Состояние сохранено\n")
# Третье редактирование без сохранения
editor.write(" Это новый текст.")
print(f"Текущее содержимое: {editor.get_content()}")
print("❌ Текущая правка не сохранена\n")
# Операция отмены - восстановление последнего сохранённого состояния
memento = history.pop()
if memento:
editor.restore(memento)
print(f"Содержимое после отмены: {editor.get_content()}")
print("↩️ Отменено до предыдущей точки сохранения\n")
# Отменить ещё раз
memento = history.pop()
if memento:
editor.restore(memento)
print(f"Содержимое после повторной отмены: {editor.get_content()}")
print("↩️ Отменено до исходного состояния")
# Запустите демо
if __name__ == "__main__":
memo_pattern_demo()
Результат выполнения:

Продвинутое применение: Memento, поддерживающий несколько состояний
В реальных приложениях может потребоваться сохранить несколько атрибутов объекта. Вот более сложный пример:
Пример
import json
class GameStateMemento:
"""Снимок состояния игры"""
def __init__(self, level, score, player_health, inventory):
self._state = {
'level': level,
'score: score,
'player_health': player_health,
inventory': inventory.copy(), # Создать копию, чтобы избежать проблем со ссылками
'timestamp': timetime.now.isoformat().isoformat()
}
def get_state(self):
"""Получить полное состояние"""
return self._state.copy()
def get_timestamp(self):
"""Получить время сохранения"""
return self._state['timestamp']
class Game:
"""Класс Game - Создатель"""
def __init__(self):
self.level = 1
self.score = 0
self.player_health = 100
self.inventory = ['sword', 'potion']
def play(self, level_increase=1, score_increase=100, health_change=0, new_item=None):
"""Симулировать игровой процесс"""
self.level += level_increase
self.score += score_increase
self.player_health += health_change
if new_item and new_item not in self.inventory:
self.inventory.append(new_item)
# Здоровье не может быть отрицательным
self.player_health = max(0, self.player_health)
def display_status(self):
"""Отобразить текущее состояние"""
status = f"""
🎮 Состояние игры:
Уровень: {self.level}
Счёт: {self.score}
Здоровье: {self.player_health}
Инвентарь: {', '.join(self.inventory)}
"""
print(status)
def save_game(self):
"""Сохранить состояние игры"""
return GameStateMemento(
self.level,
self.score,
self.player_health,
self.inventory
)
def load_game(self, memento):
"""Загрузить состояние игры"""
state = memento.get_state()
self.level = state['level']
self.score = state['score']
self.player_health = state['player_health']
self.inventory = state['inventory']
class SaveManager:
"""Менеджер сохранений"""
def __init__(self):
self.saves = {}
def create_save(self, save_name, memento):
"""Создать сохранение"""
self.saves[save_name] = memento
print(f"💾 Сохранение '{save_name}' успешно создано!")
def load_save(self, save_name):
"""Загрузить сохранение"""
if save_name in self.saves:
print(f"🔄 Загрузка сохранения '{save_name}'...")
return self.saves[save_name]
else:
print(f"❌ Сохранение '{save_name}' не существует!")
return None
def list_saves(self):
"""Список всех сохранений"""
if not self.saves:
print("📁 Нет доступных сохранений")
return
print("\n📋 Список сохранений:")
for name, memento in self.saves.items():
timestamp = memento.get_timestamp()
print(f" - {name} (Сохранено: {timestamp})")
# Демонстрация расширенного примера
def advanced_memo_demo():
print("=== Демонстрация системы сохранения игры ==="\n")
game = Game()
save_manager = SaveManager()
# Начальное состояние
print("🎯 Начинаем новую игру:")
game.display_status()
# Прогресс игры
print("🚀 Играем в игру...")
game.play(level_increase=2, score_increase=500, new_item='shield')
game.display_status()
# Сохраняем первое сохранение
save_manager.create_save("Уровень 1 завершён", game.save_game())
# Продолжаем игру
print("⚔️ Продолжаем приключения...")
game.play(level_increase=1, score_increase=200, health_change=-30, new_item='magic_wand')
game.display_status()
# Сохранить второе сохранение
save_manager.create_save("Уровень 2 начинается", game.save_game())
# Показать список сохранений
save_manager.list_saves()
# Загрузить первое сохранение
print("\n⏪ Вернуться к состоянию завершения Уровня 1:")
first_save = save_manager.load_save("Уровень 1 завершён")
if first_save:
game.load_game(first_save)
game.display_status()
# Запустить расширенный пример
if __name__ == "__main__":
advanced_memo_demo()
Преимущества и недостатки паттерна Снимок
Преимущества
- Защита инкапсуляции: не раскрывает внутреннее состояние объекта, сохраняя инкапсуляцию
- Упрощение создателя: отделяет логику сохранения состояния от бизнес-логики
- Простота реализации восстановления: предоставляет простой механизм восстановления состояния
- Поддержка множественной отмены: позволяет сохранять состояния в разные моменты времени
Недостатки
- Потребление памяти: если состояние большое или сохранения частые, это потребляет много памяти
- Накладные расходы опекуна: требует дополнительного класса для управления снимками
- Управление жизненным циклом: необходимо правильно обрабатывать создание и уничтожение снимков
Лучшие практики и меры предосторожности
1. Стратегия оптимизации памяти
Пример
class LimitedHistoryManager:
def __init__(self, max_size=10):
self._mementos = []
self._max_size = max_size
def push(self, memento):
if len(self._mementos) >= self._max_size:
# Удалить самую старую запись
self._mementos.pop(0)
self._mementos.append(memento)
2. Обработка сложных состояний объектов
Для объектов со сложными ссылками убедитесь, что снимок создаёт глубокую копию:
Пример
class ComplexMemento:
def __init__(self, complex_state):
# Использовать глубокое копирование, чтобы избежать проблем со ссылками
self._state = copy.deepcopy(complex_state)
3. Выборочное сохранение
Не все состояния нужно сохранять; можно выбрать сохранение только важных свойств:
Пример
"""Выборочно сохранить важное состояние"""
important_state = {
'essential_data': self.essential_data,
'critical_settings': self.critical_settings
# Игнорировать временные данные и кэш
}
return SelectiveMemento(important_state)
Реальные сценарии применения
1. Графический редактор
Пример
def save_state(self):
return GraphicMemento(
self.selected_shapes.copy(),
self.canvas_state,
self.view_settings
)
2. Менеджер конфигурации
Пример
def backup_config(self):
return ConfigMemento(
self.current_settings.copy(),
self.user_preferences
)
3. Операции с транзакциями
Пример
def create_checkpoint(self):
return TransactionMemento(
self.connection_state,
self.pending_operations.copy()
)
Итог
Паттерн Хранитель — это мощный и практичный паттерн проектирования, который предоставляет элегантный механизм для сохранения и восстановления состояния. Отделяя логику сохранения состояния от бизнес-логики, он не только сохраняет код чистым, но и повышает поддерживаемость системы.
Ключевые моменты:
- Ядро паттерна Хранитель — это механизм «сохранение-восстановление».
- Три ключевые роли выполняют свои обязанности с чётким распределением ответственности.
- На практике обращайте внимание на управление памятью и выбор состояния.
- Подходит для сценариев, требующих отмены, повтора или отката состояния.