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

Паттерн Хранитель (Memento) позволяет выносить внутреннее состояние объекта за его пределы для последующего возможного восстановления объекта без нарушения принципа инкапсуляции.

Когда использовать Memento?

  • Когда нужно сохранить состояние объекта для возможного последующего восстановления
  • Когда сохранение состояния должно проходить без нарушения принципа инкапсуляции

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

С помощью диаграмм структуру паттерна можно изобразить следующим образом:

Паттерн Memento в C# и .NET

Формальная структура паттерна на языке C#:

class Memento
{
    public string State { get; private set;}
    public Memento(string state)
    {
        this.State = state;
    }
}
 
class Caretaker
{
    public Memento Memento { get; set; }
}
 
class Originator
{
    public string State { get; set; }
    public void SetMemento(Memento memento)
    {
        State = memento.State;
    }
    public Memento CreateMemento()
    {
        return new Memento(State);
    }
}

Участники

  • Memento: хранитель, который сохраняет состояние объекта Originator и предоставляет полный доступ только этому объекту Originator
  • Originator: создает объект хранителя для сохранения своего состояния
  • Caretaker: выполняет только функцию хранения объекта Memento, в то же время у него нет полного доступа к хранителю и никаких других операций над хранителем, кроме собственно сохранения, он не производит

Теперь рассмотрим реальный пример: нам надо сохранять состояние игрового персонажа в игре:

class Program
{
    static void Main(string[] args)
    {
        Hero hero = new Hero();
        hero.Shoot(); // делаем выстрел, осталось 9 патронов
        GameHistory game = new GameHistory();
             
        game.History.Push(hero.SaveState()); // сохраняем игру
 
        hero.Shoot(); //делаем выстрел, осталось 8 патронов
 
        hero.RestoreState(game.History.Pop());
 
        hero.Shoot(); //делаем выстрел, осталось 8 патронов
 
        Console.Read();
    }
}
 
// Originator
class Hero
{
    private int patrons=10; // кол-во патронов
    private int lives=5; // кол-во жизней
 
    public void Shoot()
    {
        if (patrons > 0)
        {
            patrons--;
            Console.WriteLine("Производим выстрел. Осталось {0} патронов", patrons);
        }
        else
            Console.WriteLine("Патронов больше нет");
    }
    // сохранение состояния
    public HeroMemento SaveState()
    {
        Console.WriteLine("Сохранение игры. Параметры: {0} патронов, {1} жизней", patrons, lives);
        return new HeroMemento(patrons, lives);
    }
 
    // восстановление состояния
    public void RestoreState(HeroMemento memento)
    {
        this.patrons=memento.Patrons;
        this.lives = memento.Lives;
        Console.WriteLine("Восстановление игры. Параметры: {0} патронов, {1} жизней", patrons, lives);
    }
}
// Memento
class HeroMemento
{
    public int Patrons { get; private set; }
    public int Lives { get; private set; }
 
    public HeroMemento(int patrons, int lives)
    {
        this.Patrons = patrons;
        this.Lives = lives;
    }
} 
   
// Caretaker
class GameHistory
{
    public Stack<HeroMemento> History { get; private set; }
    public GameHistory()
    {
        History = new Stack<HeroMemento>();
    }
}

Консольный вывод программы:

Производим выстрел. Осталось 9 патронов
Сохранение игры. Параметры: 9 патронов, 5 жизней
Производим выстрел. Осталось 8 патронов
Восстановление игры. Параметры: 9 патронов, 5 жизней
Производим выстрел. Осталось 8 патронов

Здесь в роли Originator выступает класс Hero, состояние которого описывается количество патронов и жизней. Для хранения состояния игрового персонажа предназначен класс HeroMemento. С помощью метода SaveState() объект Hero может сохранить свое состояние в HeroMemento, а с помощью метода RestoreState() — восстановить.

Для хранения состояний предназначен класс GameHistory, причем все состояния хранятся в стеке, что позволяет с легкостью извлекать последнее сохраненное состояние.

Использование паттерна Memento дает нам следующие преимущества:

  • Уменьшение связанности системы
  • Сохранение инкапсуляции информации
  • Определение простого интерфейса для сохранения и восстановления состояния

В то же время мы можем столкнуться с недостатками, в частности, если требуется сохранение большого объема информации, то возрастут издержки на хранение всего объема состояния.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *