Wzorce projektowe: Memento

by Piotrosz 17. June 2009
Wzorzec memento pozwala na przechwycenie stanu obiektu i zachowanie go gdzieś na zewnątrz tego obiektu, dzięki czemu może być on później odtworzony.

Struktura jest dość prosta; składa się z 3 elementów (klas):

Originator - ma obiekty, których stan powinien być zachowany
Memento - odpowiada za zapisywanie stanu
Caretaker - dba o różne zachowane stany

Przykład W tym przykładzie zastosowanie typu generycznego pozwala na zachowanie stanu obiektu dowolnej klasy.
using System;
using System.Collections.Generic;

namespace MementoExample
{
    // Posiada stan, który powinien być zachowany
    class Originator<T>
    {
        public T State { get; set; }

        public Memento<T> GetMemento()
        {
            return (new Memento<T>(State));
        }

        public void RestoreMemento(Memento<T> memento)
        {
            this.State = memento.State;
        }
    }

    // Potrafi zapisać stan
    class Memento<T>
    {
        public T State { get; private set; }
        public Memento(T state)
        {
            this.State = state; // Zapisanie stanu w konstruktorze
        }
    }

    // Zarządza zachowanymi stanami
    class Caretaker<T>
    {
        // taka deklaracja pozwoli na zachowanie 1 stanu 
        //public Memento<T> Memento { get; set; }

        // tutaj można zapisać wiele poprzednich stanów i raz je odtworzyć
        private Stack<Memento<T>> _Mementos = new Stack<Memento<T>>();
        public Memento<T> Memento
        {
            get { return this._Mementos.Pop(); }
            set { this._Mementos.Push(value); }
        }
    }

    // Stan - jakaś dowolna klasa
    class Person
    {
        public Person(int Age, string Name)
        {
            this.Age = Age;
            this.Name = Name;
        }
        int Age { get; set; }
        string Name { get; set; }

        public override string ToString()
        {
            return string.Format("[{0} {1}]", this.Age, this.Name);
        }
    }

    class MainClass
    {
        public static void Main()
        {
            // *** Zapisanie i odtworzenie jednego stanu ***
            Originator<Person> org = new Originator<Person>();
            org.State = new Person(12, "Ala");
            Caretaker<Person> caretaker = new Caretaker<Person>();
            // Zapisz stan obiektu w Caretaker
            caretaker.Memento = org.GetMemento();
            // Stan początkowy
            Console.WriteLine(org.State.ToString());
            org.State = new Person(99, "Adam");
            // Kolejny stan
            Console.WriteLine(org.State.ToString());
            // Odtwórz stan poprzez Caretaker
            org.RestoreMemento(caretaker.Memento);
            // Odtworzony stan początkowy
            Console.WriteLine(org.State.ToString());

            // *** Odtworzenie wielu stanów ***
            org.State = new Person(13, "AAAA");
            caretaker.Memento = org.GetMemento();
            org.State = new Person(14, "BBBB");
            caretaker.Memento = org.GetMemento();
            org.State = new Person(15, "CCCC");
            
            // Ostatni stan
            Console.WriteLine("\n{0}", org.State);
            org.RestoreMemento(caretaker.Memento);
            // Wcześniejszy stan
            Console.WriteLine(org.State.ToString());
            org.RestoreMemento(caretaker.Memento);
            // Jeszcze wcześniejszy stan
            Console.WriteLine(org.State.ToString());
        }
    }
}

Wzorce projektowe: Observer

by Piotrosz 1. June 2009
Po beznadziejnym Visitorze, dla którego ciężko znaleźć jakieś praktyczne zastosowanie, w miarę prosty i bardzo często używany wzorzec: Observer.

Przydaje się on wtedy, gdy jest obiekt, który zmienia swój stan i zachodzi potrzeba, aby śledzić te zmiany. Czyli zdefiniowana tu jest relacja jeden do wielu: jeden obiekt (Subject) zmienia swój stan, a inne obiekty (Observers) są o tym przez niego powiadamiane.

Bardzo łatwo podać jakiś przykład zastosowania tego wzorca. Subject to na przykład autor bloga, który publikuje nowy wpis i powiadamia o tym swoich czytelników (obserwatorów).

Implementując ten wzorzec w C# można skorzystać z delegatów (delegates) i zdarzeń (events). Delegat to bezpieczny (type safe) wskaźnik na metodę. Czyli jest to coś co trzyma referencję do metody (deklaracja wygląda tak: delegate ZwracanyTyp NazwaDelegata ([Parametry])). Zdarzenie to element, który pozwala na wysyłanie powiadomienia (deklaracja wygląda tak: event NazwaDelegata NazwaZdarzenia).

Przykład:
using System;

namespace ObserverExample
{
    // Subject
    public class Thermometer
    {
        public delegate void TemperatureDelegate(object Temperature); // delegat (wskaznik na metode przyjmujaca jako arg. 

1 obiekt i nic nie zwracającą)
        public event TemperatureDelegate TemperatureChanged; // zdarzenie (powiadomienie)

        object _Temperature;
        public object Temperature
        {
            set
            {
                _Temperature = value;
                if(TemperatureChanged != null)
                    TemperatureChanged(_Temperature); // odpalenie zdarzenia (czyli powiadomienie obserwatorów)
            }
        }
    }

    // Observer: reprezentuje interfejs użytkownika 
    public class TemperatureDisplay 
    {
        public void TemperatureChanged(object Temperature)
        {
            Console.WriteLine("Temperature: {0}", Temperature);
        }
    }

    public class Program
    {
        public static void Main()
        {
            TemperatureDisplay TempDisp = new TemperatureDisplay(); // (Observer)
            Thermometer Therm = new Thermometer(); // (Subject/Observable)
            // przypisanie delegata do odpowiedniej metody
            Thermometer.TemperatureDelegate TDelegate = new Thermometer.TemperatureDelegate(TempDisp.TemperatureChanged);
            Therm.TemperatureChanged += TDelegate; // dodaje delegata do zdarzenia

            // zmiany temperatury
            Therm.Temperature = 20;
            Therm.Temperature = 22;
            Therm.Temperature = -10;

            Therm.TemperatureChanged -= TDelegate; // usuwanie delegata

            Therm.Temperature = 30; // Brak powiadomienia
        }
    }
}

Powered by BlogEngine.NET 1.5.0.7
Theme by Mads Kristensen