Wzór pamiątkowy - Memento pattern

Wzór memento jest wzorzec projektowania oprogramowania , które zapewnia możliwość przywrócenia obiektu do stanu poprzedniego ( cofanie poprzez rollback).

Wzorzec memento realizowany jest za pomocą trzech obiektów: inicjatora , opiekuna i memento . Inicjatorem jest jakiś obiekt, który ma stan wewnętrzny. Opiekun zamierza coś zrobić z pomysłodawcą, ale chce mieć możliwość cofnięcia zmiany. Opiekun najpierw prosi nadawcę o przedmiot pamiątkowy. Następnie wykonuje dowolną operację (lub sekwencję operacji), którą zamierzał wykonać. Aby przywrócić stan sprzed operacji, zwraca obiekt memento do twórcy. Sam przedmiot pamiątkowy jest obiektem nieprzezroczystym (którym dozorca nie może lub nie powinien zmieniać). Używając tego wzorca, należy zwrócić uwagę, czy twórca może zmienić inne obiekty lub zasoby — wzorzec memento działa na pojedynczym obiekcie.

Klasyczne przykłady wzorca memento obejmują generator liczb pseudolosowych (każdy konsument PRNG służy jako dozorca, który może zainicjować PRNG (inicjatora) tym samym ziarnem (pamiątka) w celu wytworzenia identycznej sekwencji liczb pseudolosowych) oraz stan w skończonej maszynie stanowej .

Przegląd

Wzorzec projektowy Memento jest jednym z dwudziestu trzech dobrze znanych wzorców projektowych GoF, które opisują sposób rozwiązywania powtarzających się problemów projektowych w celu projektowania elastycznego i wielokrotnego użytku oprogramowania zorientowanego obiektowo, czyli obiektów, które są łatwiejsze do wdrożenia, zmiany, testowania i ponowne użycie. Wzór Memento został stworzony przez Noah Thompsona, Davida Espiritu i dr Drew Clinkenbeard dla wczesnych produktów HP.

Jakie problemy może rozwiązać wzorzec projektowy Memento?

  • Wewnętrzny stan obiektu powinien być zeskładowany zewnętrznie, aby można go było później przywrócić do tego stanu.
  • Enkapsulacja obiektu nie może zostać naruszona.

Problem polega na tym, że dobrze zaprojektowany obiekt jest hermetyzowany tak, że jego reprezentacja (struktura danych) jest ukryta wewnątrz obiektu i nie można do niej uzyskać dostępu z zewnątrz obiektu.

Jakie rozwiązanie opisuje wzorzec projektowy Memento?

Uczynić przedmiot (twórca) sam odpowiedzialny za

  • zapisanie jego stanu wewnętrznego do obiektu (pamiątki) i
  • przywracanie poprzedniego stanu z obiektu (pamiątki).

Tylko nadawca, który utworzył pamiątkę, ma do niej dostęp.

Klient (opiekun) może zażądać memento od nadawcy (w celu zapisania wewnętrznego stanu nadawcy) i przekazać pamiątkę z powrotem do nadawcy (w celu przywrócenia poprzedniego stanu).

Pozwala to na zapisanie i odtworzenie stanu wewnętrznego twórcy bez naruszania jego enkapsulacji.

Zobacz także diagram klas i sekwencji UML poniżej.

Struktura

Diagram klas i sekwencji UML

Przykładowy diagram klas i sekwencji UML dla wzorca projektowego Memento.

W powyższym UML schemacie klasy The Caretakergrupa odnosi się do Originatorklasy oszczędzania ( createMemento()) i przywracania ( restore(memento)stan wewnętrzny) wytwórcy na.
Te Originatornarzędzia klasy
(1) createMemento()poprzez tworzenie i powrocie do Mementoobiektu, który obecny stan wewnętrzny sklepach inicjującego i
(2) restore(memento)przez przywrócenie stanu z przekazywane w Mementoobiekcie.

W UML Schemat sekwencji pokazuje interakcje run-time:
stan wewnętrzny (1) oszczędność inicjującego: Do Caretakerpołączenia obiektu createMemento()na Originatorobiekt, który tworzy Mementoobiekt, oszczędza swój aktualny stan wewnętrzny ( setState()) i zwraca Mementosię do Caretaker.
Stan wewnętrzny (2) przywrócenie inicjującego: Do Caretakerrozmowy restore(memento)na temat Originatorobiektu i określa Mementoobiekt, który przechowuje państwo, które powinny zostać przywrócone. OriginatorDostaje stanu ( getState()) od Mementoustawić swój stan.

Przykład Javy

Poniższy program Java ilustruje użycie wzorca memento do "cofania".

import java.util.List;
import java.util.ArrayList;
class Originator {
    private String state;
    // The class could also contain additional data that is not part of the
    // state saved in the memento..
 
    public void set(String state) {
        this.state = state;
        System.out.println("Originator: Setting state to " + state);
    }
 
    public Memento saveToMemento() {
        System.out.println("Originator: Saving to Memento.");
        return new Memento(this.state);
    }
 
    public void restoreFromMemento(Memento memento) {
        this.state = memento.getSavedState();
        System.out.println("Originator: State after restoring from Memento: " + state);
    }
 
    public static class Memento {
        private final String state;

        public Memento(String stateToSave) {
            state = stateToSave;
        }
 
        // accessible by outer class only
        private String getSavedState() {
            return state;
        }
    }
}
 
class Caretaker {
    public static void main(String[] args) {
        List<Originator.Memento> savedStates = new ArrayList<Originator.Memento>();
 
        Originator originator = new Originator();
        originator.set("State1");
        originator.set("State2");
        savedStates.add(originator.saveToMemento());
        originator.set("State3");
        // We can request multiple mementos, and choose which one to roll back to.
        savedStates.add(originator.saveToMemento());
        originator.set("State4");
 
        originator.restoreFromMemento(savedStates.get(1));   
    }
}

Dane wyjściowe to:

Originator: Setting state to State1
Originator: Setting state to State2
Originator: Saving to Memento.
Originator: Setting state to State3
Originator: Saving to Memento.
Originator: Setting state to State4
Originator: State after restoring from Memento: State3

Ten przykład używa ciągu jako stanu, który jest niezmiennym obiektem w Javie. W rzeczywistych scenariuszach stan będzie prawie zawsze obiektem zmiennym, w takim przypadku należy wykonać kopię stanu.

Trzeba powiedzieć, że pokazana implementacja ma wadę: deklaruje klasę wewnętrzną. Byłoby lepiej, gdyby ta strategia memento mogła dotyczyć więcej niż jednego pomysłodawcy.

Są głównie trzy inne sposoby na osiągnięcie Memento:

  1. Serializacja.
  2. Klasa zadeklarowana w tym samym pakiecie.
  3. Dostęp do obiektu można również uzyskać za pośrednictwem serwera proxy, który może wykonać dowolną operację zapisywania/przywracania obiektu.

Przykład C#

Wzorzec memento pozwala uchwycić stan wewnętrzny obiektu bez naruszania enkapsulacji, dzięki czemu później można cofnąć/cofnąć zmiany, jeśli zajdzie taka potrzeba. Tutaj widać, że obiekt memento jest faktycznie używany do cofania zmian dokonanych w obiekcie.

class Memento
{
    private readonly string savedState;

    private Memento(string stateToSave)
    {
        savedState = stateToSave;
    }

    public class Originator
    {
        private string state;
        // The class could also contain additional data that is not part of the
        // state saved in the memento.

        public void Set(string state)
        {
            Console.WriteLine("Originator: Setting state to " + state);
            this.state = state;
        }

        public Memento SaveToMemento()
        {
            Console.WriteLine("Originator: Saving to Memento.");
            return new Memento(state);
        }

        public void RestoreFromMemento(Memento memento)
        {
            state = memento.savedState;
            Console.WriteLine("Originator: State after restoring from Memento: " + state);
        }
    }
}

class Caretaker
{
    static void Main(string[] args)
    {
        List<Memento> savedStates = new List<Memento>();

        Memento.Originator originator = new Memento.Originator();
        originator.Set("State1");
        originator.Set("State2");
        savedStates.Add(originator.SaveToMemento());
        originator.Set("State3");
        // We can request multiple mementos, and choose which one to roll back to.
        savedStates.Add(originator.SaveToMemento());
        originator.Set("State4");

        originator.RestoreFromMemento(savedStates[1]);
    }
}

Przykład w Pythonie

"""
Memento pattern example.
"""


class Memento:
    def __init__(self, state) -> None:
        self._state = state

    def get_saved_state(self):
        return self._state


class Originator:
    _state = ""

    def set(self, state) -> None:
        print("Originator: Setting state to", state)
        self._state = state

    def save_to_memento(self) -> Memento:
        print("Originator: Saving to Memento.")
        return Memento(self._state)

    def restore_from_memento(self, memento) -> None:
        self._state = memento.get_saved_state()
        print("Originator: State after restoring from Memento:", self._state)


saved_states = []
originator = Originator()
originator.set("State1")
originator.set("State2")
saved_states.append(originator.save_to_memento())

originator.set("State3")
saved_states.append(originator.save_to_memento())

originator.set("State4")

originator.restore_from_memento(saved_states[1])

Bibliografia

Linki zewnętrzne