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());
}
}
}
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
}
}
}