sobota, 6 marca 2010

Memento Pattern

Często stajemy przed problemem, kiedy chcemy udostępnić użytkownikowi naszej aplikacji cofnięcie jakiejś decyzji. Największe znaczenie taka operacja pełni w grach komputerowych, gdzie zapisywane są tzw. checkpoints dzięki którym gracz jest w stanie cofnąć się do wcześniejszej fazy gry.
W swoim aktualnym projekcie, potrzebowałem stworzyć mechanizm umożliwiający cofnięcie zmian wprowadzonych w obrazie. Dzięki wzorcowi Memento można w bardzo łatwy i szybki sposób uzyskać taką funkcjonalność. 


Co nam daje Memento?
Pozwala na zachowanie całego stanu obiektu (lub tyle ile potrzebujemy), w celu późniejszego jego odtworzenia, bez naruszenia zasad enkapsulacji.
Spójrzmy na poniższy diagram klas:

Mamy tutaj 3 klasy:
Orginator - jest to obiekt którego stan wewnętrzny chcemy zachować. Poprzez metodę CreateMemennto() tworzy "kopię zapasową" samego siebie a dzięki metodzie SetMemento(Memento) może się odtworzyć z kopi. 

Memento - obiekt który jest kopią oryginalnego obiektu. Dzięki metodą GetState() i SetState() pozwalają na odczyt i zapis stanu obiektu.



Caretaker - obiekt ten zarządza obiektami Memento


Przykłady zawsze lepiej działają na wyobraźnię, dlatego spójrzmy na oto taki przykład. Mamy aplikację w której użytkownik wypełnia jakieś pola. Niech to będzie Imię, Nazwisko i wiek. Przyjmijmy, że wygląda ona tak:


Przyjmijmy, że po naciśnięciu przycisku dane zapisują się do bazy danych. Teraz nasz użytkownik wpisuje swoje dane, wciska ok i nagle przypomina mu się że źle wpisał nazwisko. Są dwa wyjścia: wpisze wszystko od początku albo kliknie przycisk cofnij i wpisze poprawne dane. W tym konkretnym przypadku nie wiele będzie kosztowała go pomyłka, jeżeli jednak pól by było więcej to możliwość cofnięcia wprowadzonych danych będzie z pewnością bardzo przydatna. Kod aplikacji:

    public class Person

    {

        private string _firstName;

        private string _lastName;

        private int _age;

 

        public string FirstName

        {

            get

            {

                return _firstName;

            }

            set

            {

                _firstName = value;

            }

        }

 

        public string LastName

        {

            get

            {

                return _lastName;

            }

            set

            {

                _lastName = value;

            }

        }

 

        public int Age

        {

            get

            {

                return _age;

            }

            set

            {

                _age = value;

            }

        }

 

        /// <summary>

        /// Przywraca poprzedni stan obiektu

        /// </summary>

        /// <param name="memento"></param>

        public void SetMemento(Memento memento)

        {

            this._age = memento.Age;

            this._firstName = memento.FirstName;

            this._lastName = memento.LastName;

        }

 

        /// <summary>

        /// Zapisuje aktualny stan obiektu

        /// </summary>

        /// <returns></returns>

        public Memento CreateMemento()

        {

            return new Memento(_firstName, _lastName, _age);

        }

    }


    public class Memento

    {

        private string _firstName;

        private string _lastName;

        private int _age;

 

        public string FirstName

        {

            get

            {

                return _firstName;

            }

            set

            {

                _firstName = value;

            }

        }

 

        public string LastName

        {

            get

            {

                return _lastName;

            }

            set

            {

                _lastName = value;

            }

        }

 

        public int Age

        {

            get

            {

                return _age;

            }

            set

            {

                _age = value;

            }

        }

 

        public Memento(string firstName, string lastName, int age)

        {

            _firstName = firstName;

            _lastName = lastName;

            _age = age;

        }

 

 

    }


    public class Caretaker

    {

        private Memento _memento;

        public Memento Memento

        {

            get

            {

                return _memento;

            }

            set

            {

                _memento = value;

            }

        }

    }



    public partial class Form1 : Form
    {
        Person person;
        Caretaker[] caretaker;
        int counter = 0;
        public Form1()
        {
            InitializeComponent();
            person = new Person();
            caretaker = new Caretaker[10];
        }

        private void DisplayPersonInformation()
        {
            textBox1.Text = person.FirstName;
            textBox2.Text = person.LastName;
            textBox3.Text = person.Age.ToString();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (counter<10)
            {
                caretaker[counter] = new Caretaker();
                caretaker[counter].Memento = person.CreateMemento();
                counter++;
                person.FirstName = textBox1.Text;
                person.LastName = textBox2.Text;
                person.Age = int.Parse(textBox3.Text);
            }

        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (counter>1)
            {
                counter--;
                person.SetMemento(caretaker[counter].Memento);
                DisplayPersonInformation();
            }
        }
    }

Przykład być może nie jest najwyższych lotów, jednak w prosty sposób obrazuje możliwość wykorzystania tego wzorca w swoich projektach. Jak pisałem wcześniej w swoim projekcie potrzebowałem możliwości cofania zmian wprowadzanych na obrazie. Dla takich zastosowań memento nadaje się idealnie :)

Brak komentarzy:

Prześlij komentarz