Приведи пример реализации сохранения и восстановления состояния объекта (паттерн Memento).

«Приведи пример реализации сохранения и восстановления состояния объекта (паттерн Memento).» — вопрос из категории Архитектура, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Паттерн Memento позволяет сохранять и восстанавливать внутреннее состояние объекта, не нарушая его инкапсуляцию. Это основа для реализации функциональности Undo/Redo, снапшотов или контрольных точек.

Классическая реализация на C#:

// 1. Хранитель (Memento) — иммутабельный объект, хранящий снимок состояния.
public class TextMemento
{
    public string Content { get; }
    public DateTime CreatedAt { get; }

    public TextMemento(string content)
    {
        Content = content;
        CreatedAt = DateTime.UtcNow;
    }
}

// 2. Создатель (Originator) — объект, состояние которого нужно сохранять.
public class TextDocument
{
    private string _content;

    public string Content
    {
        get => _content;
        set
        {
            Console.WriteLine($"Document content changed to: {value}");
            _content = value;
        }
    }

    // Создать снимок текущего состояния
    public TextMemento CreateMemento()
    {
        return new TextMemento(_content);
    }

    // Восстановить состояние из снимка
    public void RestoreMemento(TextMemento memento)
    {
        _content = memento.Content;
        Console.WriteLine($"Document restored to state from {memento.CreatedAt}");
    }
}

// 3. Опекун (Caretaker) — управляет историей снимков.
public class HistoryTracker
{
    private readonly Stack<TextMemento> _history = new Stack<TextMemento>();

    public void SaveState(TextDocument document)
    {
        _history.Push(document.CreateMemento());
    }

    public void Undo(TextDocument document)
    {
        if (_history.Count > 0)
        {
            document.RestoreMemento(_history.Pop());
        }
    }
}

// Пример использования
class Program
{
    static void Main()
    {
        var doc = new TextDocument();
        var history = new HistoryTracker();

        doc.Content = "Version 1";
        history.SaveState(doc); // Сохраняем

        doc.Content = "Version 2";
        history.SaveState(doc); // Сохраняем

        doc.Content = "Version 3";
        Console.WriteLine($"Current: {doc.Content}"); // Version 3

        history.Undo(doc); // Откат к Version 2
        Console.WriteLine($"After undo: {doc.Content}"); // Version 2

        history.Undo(doc); // Откат к Version 1
        Console.WriteLine($"After second undo: {doc.Content}"); // Version 1
    }
}

Почему это важно:

  • Инкапсуляция: Объект TextDocument сам создаёт и читает снимки, его внутреннее представление состояния (_content) остаётся приватным.
  • Гибкость: Опекун (HistoryTracker) может хранить историю в стеке (для Undo), очереди или любом другом хранилище, не влияя на создателя.
  • Расширяемость: В снимок можно добавлять метаданные (например, timestamp), а также реализовать сжатие или сериализацию состояния для сохранения на диск.