Pamiątka

Wzorce projektowe

  |   8 min czytania

Zastosowanie

Pamiątka (ang. Memento) (wzorzec behawioralny) pozwala zachować stan lub stany obiektu w celu jego późniejszego przywrócenia. Obiekt nadawcy (Originator) zapisuje i odtwarza swój stan wykorzystując w tym celu dostarczony przez opiekuna obiekt pamiątki (Memento). Obiekt opiekuna (Caretraker) jest odpowiedzialny za przechowywanie stanów obiektu nadawcy jednakże nie ma on dostępu do jego danych. Dzięki temu nie narusza hermetyzacji obiektu nadawcy, a także upraszcza jego kod poprzez umożliwienie mu zachowanie jego stanu w historii.

Ograniczenia

Zapamiętanie wielu stanów dla złożonych obiektów może wymagać dużej ilości pamięci. Ponadto obiekt opiekuna powinien śledzić cykl życia obiektu nadawcy w celu usunięcia przestarzałych migawek.

Użycie

Wzorzec Pamiątka wykorzystywany jest przede wszystkim do przechowywania migawek z cyklu życia obiektu (historii) w celu póżniejszego przywrócenia jego stanu z uwzględnieniem ochrony dostępu spoza obiektu.

Implementacja

Klasa Originator reprezentuje obiekt kontekstu operacyjnego ze zdolnością do odtwarzania swojego stanu za pomocą obiektu klasy Memento, która przechowuje dane obiektu klasy Originator. Widoczność klasy Memento jest ograniczona, a modyfikacja jej struktury dozwolona tylko z poziomu klasy Originator. Caretaker przechowuje migawki stanu obiektu Originator w postaci obiektów Memento.

Pamiątka diagram

Poniższy listing przedstawia implementację wzorca Pamiątka dla obiektu Originator składającego się z jednego pola stanu.

public class Caretaker {

    private List<Memento> mementos = new ArrayList<>();

    public void add(Memento memento) {
        mementos.add(memento);
    }

    public Memento get(int index) {
        return mementos.get(index)
    }

    public int getSize() {
        return mementos.size();
    }
}

public class Originator {

    private String state;

    public void setState(String state) {
        this.state = state;
    }

    public Memento save() {
        return new Memento(state);
    }

    public void restore(Memento memento) {
        state = memento.getState();
    }

    class Memento {

        private String state;

        public Memento(String state) {
            this.state = state;
        }

        public String getState() {
            return state;
        }
    }
}

Klient tworzy obiekt dla którego będzie możliwe zarządzanie jego stanem. Następnie dokonuje na nim operacji zmieniając jego stan, a także zarządza procesem przywracania obiektu do wybranego stanu.

Originator originator = new Originator();
Caretaker caretaker = new Caretaker();

//first step, no need to add to the history
originator.setState("State1");

//next step, need to add to the history
originator.setState("State2");
caretaker.add(originator.save()); //State2 has been saved

//next step, also need to save
originator.setState("State3");
caretaker.add(originator.save()); //State3 has been saved

//next step, no need to save
originator.setState("State4");

//user decide to back step so previous state restore
originator.restore(caretaker.get(caretaker.getSize() - 1));

Przykład

Aplikacja DocumentWriter umożliwia tworzenie dokumentów tekstowych, a także udostępnia użytkownikowi możliwość autozapisu edytowanego dokumentu oraz przywracanie jego stanu do wybranej migawki. Użytkownik może przywrócić stan tylko zapisanego (ręcznie lub automatycznie) dokumentu. Lista migawek opatrzona datą jej stworzenia dostępna jest w panelu bocznym użytkownika. Do realizacji tego zadania został wybrany wzorzec Pamiątka.

public class DocumentManager {

    private List<Memento> mementos = new ArrayList<>();
    private int current = 0;

    public void add(Memento memento) {
        mementos.add(memento);
        current++;
    }

    public Memento redo() {
        if(current < mementos.size() - 1)
            current += 1;
        return mementos.get(current);
    }

    public Memento undo() {
        if(current > 0)
            current -= 1;
        return mementos.get(current);
    }

    public Memento get(int number) {
        if(number >= 0 && number < mementos.size())
          return mementos.get(number);
        else
          return mementos.get(current);
    }

    public int historySize() {
        return mementos.size();
    }
}

public class Document {

    private String text;
    private String name;
    private long timestamp;

    public Answer(String name) {
        this.name = name;
        this.text = "";
        this.timestamp = System.currentTimeMillis();
    }

    public void setText(String text) {
        this.text = text;
        this.timestamp = System.currentTimeMillis();
    }

    public void setName(String name) {
        this.name = name;
        this.timestamp = System.currentTimeMillis();
    }

    public Memento save() {
        return new Memento(name, text, timestamp);
    }

    public void restore(Memento memento) {
        this.name = memento.name;
        this.text = memento.text;
        this.timestamp = memento.timestamp;
    }

    class Memento {

        private String name;
        private String text;
        private long timestamp;

        public Memento(String name, String text, long timestamp) {
            this.name = name;
            this.text = text;
            this.timestamp = timestamp;
        }

        public String getText() {
            return text;
        }

        public int getOption() {
            return option;
        }
    }
}

Użytkownik tworzy nowy pusty dokument, a następnie dokonuje jego edycji. W trakcie redagowania dokumentu użytkownik dokonuje jego zapisu ręcznie oraz automatycznie (co ustalony czas). W pewnym momencie użytkownik chce przywrócić stan dokumentu do jednego z poprzednio zapisanych migawek.

//user open writer editor and create new empty file
DocumentManager manager = new DocumentManager();
Document document = new Document("CV.doc");

//user wrote some text into document
decument.setText("Begin of the text document.");

//user wrote more text
document.setText("Begin of the text document. More text wrote here.");
//autosave executed
manager.add(document.save()); //first document snapshot

//user change document name and wrote more text
document.setName("CV_ENG.doc");
document.setText("Begin of the text document. More text wrote here. And more text again.")
//autosave executed
manager.add(document.save()); //second document snapshot

//user removed whole text and typed new one
document.setText("Text wrote again.");
//manual save executed
manager.add(document.save()); //third document snapshot

//after some while user decide to restore previous document snapshot
document.restore(manager.undo());

//user can write text and undo, redo or choose concrete document snapshot to revert state

Biblioteki

Implementacje interfejsu Serializable mogą być przykładem realizacji wzorca Pamiątka. Proces przywracania danych aktywności po obrocie ekranu przy użyciu obiektu Bundle symuluje zachowanie wzorca.