Паттерн Хранитель в Python (Memento)

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

Представьте, что вы играете в игру, которая предоставляет функцию «сохранения». Вы можете в любой момент сохранить текущий игровой прогресс. Если позже вы потерпите неудачу или захотите переиграть уровень, вы можете загрузить предыдущее сохранение. Паттерн Хранитель — это программное решение, реализующее эту функциональность «сохранения-загрузки».


Зачем нам нужен паттерн Хранитель?

Сценарии применения

  • Текстовый редактор: реализовать функции отмены и повтора
  • Разработка игр: сохранение игрового прогресса и состояния
  • Графическое ПО: поддержка истории операций
  • Обработка транзакций: откат к предыдущему состоянию при сбое операции

Решаемые проблемы

  • Необходимость сохранять состояние объекта, не раскрывая внутренние детали
  • Предоставить механизм восстановления состояния, поддерживающий функцию «лекарства от сожалений» (то есть возможность отмены)
  • Сохранять инкапсуляцию объекта и избегать смешивания логики сохранения состояния с бизнес-логикой

Основные компоненты паттерна Хранитель

Паттерн Хранитель состоит из трёх основных ролей:

1. Создатель

  • Объект, которому необходимо сохранить состояние
  • Создаёт снимок для фиксации текущего состояния
  • Использует снимок для восстановления предыдущего состояния

2. Хранитель

  • Хранит внутреннее состояние объекта-создателя
  • Не позволяет объектам, кроме создателя, получать доступ к содержимому снимка

3. Опекун

  • Отвечает за сохранение снимков
  • Не может изменять или просматривать содержимое снимков


Реализация паттерна Хранитель на Python

Давайте конкретно реализуем паттерн Memento на примере текстового редактора.

Базовая реализация

Пример

class TextEditorMemento:
    """Класс 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 datetime
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. Защита инкапсуляции: не раскрывает внутреннее состояние объекта, сохраняя инкапсуляцию
  2. Упрощение создателя: отделяет логику сохранения состояния от бизнес-логики
  3. Простота реализации восстановления: предоставляет простой механизм восстановления состояния
  4. Поддержка множественной отмены: позволяет сохранять состояния в разные моменты времени

Недостатки

  1. Потребление памяти: если состояние большое или сохранения частые, это потребляет много памяти
  2. Накладные расходы опекуна: требует дополнительного класса для управления снимками
  3. Управление жизненным циклом: необходимо правильно обрабатывать создание и уничтожение снимков

Лучшие практики и меры предосторожности

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. Обработка сложных состояний объектов

Для объектов со сложными ссылками убедитесь, что снимок создаёт глубокую копию:

Пример

import copy

class ComplexMemento:
    def __init__(self, complex_state):
        # Использовать глубокое копирование, чтобы избежать проблем со ссылками
        self._state = copy.deepcopy(complex_state)

3. Выборочное сохранение

Не все состояния нужно сохранять; можно выбрать сохранение только важных свойств:

Пример

def create_selective_memento(self):
    """Выборочно сохранить важное состояние"""
    important_state = {
        'essential_data': self.essential_data,
        'critical_settings': self.critical_settings
        # Игнорировать временные данные и кэш
    }
    return SelectiveMemento(important_state)

Реальные сценарии применения

1. Графический редактор

Пример

class GraphicEditor:
    def save_state(self):
        return GraphicMemento(
            self.selected_shapes.copy(),
            self.canvas_state,
            self.view_settings
        )

2. Менеджер конфигурации

Пример

class ConfigManager:
    def backup_config(self):
        return ConfigMemento(
            self.current_settings.copy(),
            self.user_preferences
        )

3. Операции с транзакциями

Пример

class DatabaseTransaction:
    def create_checkpoint(self):
        return TransactionMemento(
            self.connection_state,
            self.pending_operations.copy()
        )

Итог

Паттерн Хранитель — это мощный и практичный паттерн проектирования, который предоставляет элегантный механизм для сохранения и восстановления состояния. Отделяя логику сохранения состояния от бизнес-логики, он не только сохраняет код чистым, но и повышает поддерживаемость системы.

Ключевые моменты:

  • Ядро паттерна Хранитель — это механизм «сохранение-восстановление».
  • Три ключевые роли выполняют свои обязанности с чётким распределением ответственности.
  • На практике обращайте внимание на управление памятью и выбор состояния.
  • Подходит для сценариев, требующих отмены, повтора или отката состояния.
Другие расширения