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

Якщо ви коли небуть бавилися в «стрілялки», то дуже вірогідно, що ви знайомі із значенням хот-кіїв F5 та F9. I, навіть, якщо ви таки не мали шансу в житті погратися в «шпільки», ідея швидкого збереження поточного стану і відновлення до нього ідеологічно є знайомою (навіть якщо це було Ctrl+Z у програмі Word). Натискаючи F5 ви зберігаєте поточне місце знаходження і рівні життя/броні та, можливо, ще якусь інформацію, наприклад, скільки монстрів було вже вбито на даній позиції (напевно для того, щоб не «мочити» їх заново). Коли клавіша F9 натискається відбувається повернення до попереднього збереженого стану.Під час збереження стану, швидше за все, вам мало хочеться щоб ця інформація була доступна іншими класами (інкапсуляція стану), таким чином ви будете певні, що ніхто не зменшить рівень життя. Також, може знадобитися функціональність по збереженню послідовності станів. Наприклад можливість повернутися на 2 або 3 збереження назад натискаючи Shift+F9 (+F9). Як це можна реалізувати?

Хранитель (англ. Memento) використовується коли ви хочете відмінити операції без відображення внутрішньої структури Хазяїна (Originator - гра у нашому прикладі). Координація операцій здійснюється Опікуном (Caretaker - контроллер гри), який надає можливість простого збереження миттєвих станів системи без уявлення що ці стани собою являють.[1]

Давайте глянемо на реалізацію:

Уривок коду 18.1. Гра

public class GameOriginator

{

    // Стан містить здоров’я та к-ть вбитих монстрів

    private GameState _state = new GameState(100, 0);

 

    public void Play()

    {

      // Імітуємо процес гри –

      // здоров’я повільно погіршується, а монстрів стає все менше

      Console.WriteLine(_state.ToString());

      _state = new GameState((int)(_state.Health*0.9), _state.KilledMonsters + 2);

    }

 

 

    public GameMemento GameSave()

    {

        return new GameMemento(_state);

    }

 

    public void LoadGame(GameMemento memento)

    {

        _state = memento.GetState();

    }

}

Уривок коду 18.2. Хранитель GameMemento

public class GameMemento

{

    private readonly GameState _state;

 

    public GameMemento(GameState state)

    {

        _state = state;

    }

 

    public GameState GetState()

    {

        return _state;

    }

}

Таким чином, цей клас може згенерувати екземпляр Хранителя із поточним знімком (станом) гри, в той же час можна витягнути стан із уже існуючого Хранителя, але при цьому ніхто більше не буде працювати із станом гри напряму.

Нижче наведений код може тільки завантажити останній збережений «сейв», але все можна легко вдосконалити.

Уривок коду 18.3. Відповідальний

public class Caretaker

{

    private readonly GameOriginator _game = new GameOriginator();

    private readonly Stack< GameMemento > _quickSaves = new Stack< GameMemento >();

 

    public void ShootThatDumbAss()

    {

        _game.Play();

    }

 

    public void F5()

    {

        _quickSaves.Push(_game.GameSave());

    }

 

    public void F9()

    {

        _game.LoadGame(_quickSaves.Peek());

    }

}

 

Припустимо що гра протікала так як у наступному уривку коду.

Уривок коду 18.4. Використання

var caretaker = new Caretaker();

caretaker.F5();

caretaker.ShootThatDumbAss();

caretaker.F5();

caretaker.ShootThatDumbAss();

caretaker.ShootThatDumbAss();

caretaker.ShootThatDumbAss();

caretaker.ShootThatDumbAss();

caretaker.F9();

caretaker.ShootThatDumbAss();

В такому випадку наша демонстраційна програма згенерує такий вивід (State має перевантажений метод ToString):

Health: 100

Killed Monsters: 0

Health: 90

Killed Monsters: 2

Health: 81

Killed Monsters: 4

Health: 72

Killed Monsters: 6

Health: 64

Killed Monsters: 8

Health: 90

Killed Monsters: 2

А ось проста UML діаграма, намальована для цього прикладу:

UML-діаграма 10. Хранитель стану гри

[1] Memento. Intent. Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.

 Хранитель. Призначення. Без порушення інкапсуляції, зафіксувати та відокремети внутрішній стан об’єкта, так, що об’кт потім зможе повернутися до цього стану.