Memento Pattern nedir

Bu yazmıda Memento Pattern haqqında yazacam. Nədir Memento Pattern?, nə üçün istifadə olunur? və necə implement olunmalıdır?

Problem

Heç olubmu siz obyektin hər hansı bir property-sinin dəyərini dəyişdikdən sonra onu geri qaytarmaq istəyəsiniz?. Yəni obyekti Undo edəsiniz.

Deyək ki, bizim aşağıdakı kimi çox sadə Person adlı class-ımız və onun string tipində Name adlı property-si var.

public class Person
{
    public string Name { get; set; }
}

İndi isə main metoduna baxaq

static void Main(string[] args)
{
    var person = new Person();
    person.Name = "Ali";
    person.Name = "Vali";
    Console.WriteLine(person.Name); //nəticə Vali
    person.Undo(); //şərti yazılıb
    Console.WriteLine(person.Name); // nəticə Ali
}

Qeyd: Hələlik Person class-ının Undo metodu yoxdur.

Deməli Undo metodu işlətdikdən sonra person obyekti bir əvvəlki vəziyyətinə qayıtmalıdır. Memento Patter -in sırf bu məqsədlə hazırlanıb.

Həll

Name property-sinin bir əvvəlki dəyərini saxlamaq üçün Person class-ını aşağıdakı kimi yaza bilərik.

public class Person
{
    private string _previousName;
    public string Name { set; get; }

    public void SaveState()
    {
        _previousName= Name;
    }

    public void Undo()
    {
        Name = _previousName;
    }
}
static void Main(string[] args)
{
    var person = new Person();
    person.Name = "Ali";
    person.SaveState();
    person.Name = "Vali";
    Console.WriteLine(person.Name);//nəticə Vali
    person.Undo();
    Console.WriteLine(person.Name);// nəticə Ali
}

Proqramı işə salıb yoxlasaq hər şeyin çox gözəl işlədiyini görəcəksiz amma burada iki problem var.

  1. Biz ancaq bir dəfə geriyə qayıda bilirik. Yəni əgər biz Name -ə əvvəlcə “Ali” sonra “Vali” daha sonra isə “Mammad” set etsək biz ancaq “Vali” vəziyyətinə qayıda biləcəyik. Deməli biz Name -in arrayini saxlamalıyıq. 
  2. Person class-ı iki iş görür. Birincisi öz yaradılma məqsədli işidir. Yəni şəxslərin məlumatlarını saxlamaq və şəxsə aid əməliyyatlar yerinə yetirmək. Məsələn şəxsin doğum tarixinə görə hal-hazırdakı yaşını hesablamaq. İkinci işi isə əvvəlki vəziyyətini saxlamaqdır. Single responsibility prinsipindən bildiyimizə görə bir class bir iş görməlidir. Deməli biz əvvəlki vəziyyətini saxlamaq işini başqa bir class-da etməliyik.

PersonState adlı bir class hazırlayaq. Burada hansı property-lərin əvvəlki dəyərlərini saxlayacayıqsa onları yazmalıyıq. Biz hələlik Name property-sinin əvvəlki dəyərlərini saxlamaq isəyirik ona görə PersonState class-imizi aşağıdakı kimi yaza bilərik.

public class PersonState
{
    public string Name { get; private set; }

    public PersonState(string name)
    {
        Name = name;
    }
}

İndi isə bizə lazimdir ki, Person classının SaveState metodu çağırılıanda PersonState class-ının obyektini yaradaq və haradasa saxlayaq. Daha sonra Undo metodu işləyən zaman o hardasa saxladığımız yerdən götürüb Name property-sinin dəyərini dəyişək. Ona görə də PersonState obyektini saxlamaq üçün bizə daha bir class lazımdır. Bunu üçün PersonHistory adlı class yaradaq. Bu class-ın içerisində birdən çox geri qayıda bilmək üçün PersonState class-inin array-ini saxlamalıyıq.

public class PersonHistory
{
    private readonly List<PersonState> PersonList = new List<PersonState>();

    public void Push(PersonState state)
    {
        PersonList.Add(state);
    }

    public PersonState Pop()
    {
        var lastIndex = PersonList.Count - 1;
        var lastState = PersonList[lastIndex];
        PersonList.RemoveAt(lastIndex);
        return lastState;
    }
}

Person class-ının içərsində balaca dəyişiklik edək.

public class Person
{
    public string Name { get; set; }

    public PersonState CreateState()
    {
        return new PersonState(Name);
    }

    public void Undo(PersonState state)
    {
        Name = state.Name;
    }
}

Hazırladığımız proqramı test edək.

static void Main(string[] args)
{
     var person = new Person();
     var personHistory = new PersonHistory();

     person.Name = "Ali";
     personHistory.Push(person.CreateState());
     person.Name = "Vali";
     personHistory.Push(person.CreateState());
     person.Name = "Mammad";
     personHistory.Push(person.CreateState());
     person.Undo(personHistory.Pop());

     Console.WriteLine(person.Name);
}

Proqramı işə salıb yoxlasaq hər şey istədiyimiz kimi işləyəcək. Bu hazırladığımız Undo yanaşması Memento Pattern -dir.Burada Person class-ına Originator , PersonState-ə Memento, PersonHistory class-ına isə Caretaker deyilir.

Xüsusi yanaşma

Momento-da Undo edən zaman əvvəlki vəziyyətinə qayıtmasını istədiyimiz property-ləri yazırıq. Əgər Originator-da çox property olarsa və onların hamısının əvvəlki dəyərlərini saxlamaq istəyiriksə Memento-da onların hamısını yazmaq bir az sıxıcı ola bilər. Belə olan halda bir az fərqli yanaşmaq olar. Yuxardakı misal üzərindən getsək PersonHistory-də PersonState əvəzinə Person class-ının array-ini saxlamaq olar. Bu halda CreateState metodunun içərisində hazırda olan obyektin copyasın almaq lazımdır. Bunun üçün istəkdən asılı olaraq Shallow copy və ya Deep copy istifadə edə bilərsiz.

public class Person
{
    public string Name { get; set; }

    public object CreateState()
    {
       return this.MemberwiseClone();
    }
}


public class PersonHistory
{
    private readonly List<object> PersonList = new List<object>();

    public void Push(object editor)
    {
        PersonList.Add(editor);
    }

    public Person Pop()
    {
        var lastIndex = PersonList.Count - 1;
        var lastState = PersonList[lastIndex];
        PersonList.RemoveAt(lastIndex);
        return (Person)lastState;
    }
}

static void Main(string[] args)
{
     var person = new Person();
     var personHistory = new PersonHistory();

     person.Name = "Ali";
     personHistory.Push(person.CreateState());
     person.Name = "Vali";
     personHistory.Push(person.CreateState());
     person.Name = "Mammad";

     person = personHistory.Pop();

     Console.WriteLine(person.Name);


     Console.ReadKey();
}

Qeyd: Bu yanaşma Memento pattern deyil. Memento pattern daha elastikdir. Biz bu yanaşma ilə obyektin kopyasın alıb saxlayırıq. Memento ilə isə isədiyimiz property-ləri saxlaya bilərik.

Bir cavab yazın

Sizin e-poçt ünvanınız dərc edilməyəcəkdir. Gərəkli sahələr * ilə işarələnmişdir