Memento
Motivace – kalkulačka 210° sin * /20 ? 0.25 ^2 ?
Motivace – kalkulačka 210° sin * ^2
Motivace – zálohování databáze
Memento Známý také jako Token nebo Snapshot Kategorie: Behavioral patterns (vzory chování) Účel Uchování vnitřního stavu objektu bez porušení zapouzdření pro potřeby budoucí možnosti vrátit se k němu zpět Motivace Undo/rollback Checkpointy Dočasné operace Zotavení z chyb Použití Obnovení operačního systému Transakce v databázích Složité matematické výpočty, při kterých se můžou ztratit dlouho počítaná data Historie tahů ve hrách, načítání a ukládání stavu hry Uchování rozmístění objektů v grafických editorech
Struktura
Originator Objekt, který si chce nechat ukládat svůj vnitřní stav Memento vytváří pro uchování svého vnitřního stavu Memento využívá pro obnovení svého vnitřního stavu
Struktura Caretaker Je zodpovědný za uchování potřebných Mement S Mementem pracuje jako s černou skříňkou, nevidí na jeho obsah
Struktura Memento Reprezentuje uložený vnitřní stav Originatoru Chrání privátní data Originatora Implementuje 2 různá rozhraní: Wide interface – pro Originatora Narrow interface – pro Caretakera
Interakce Caretaker požádá Originator o Memento Nějaký čas si ho podrží Kdykoli se pak může rozhodnout Memento vrátit zpět Originatoru Originator vyrobí Memento reprezentující jeho aktuální stav Originator přečte data z daného Mementa a změní svůj stav
Vlastnosti + Stav se uchovává mimo vlastní objekt, zjednodušuje se tím Originator Zbavuje odpovědnosti v Originatoru za zabezpečení přístupu k uloženým datům Privátní data Originatoru uchovaná v Mementu jsou čitelná jen pro Originator samotný Zabraňuje odhalení vnitřních implementace Originatoru Caretaker může aplikovat různé strategie pro uchovávání a zahazování Mement Prakticky neomezená kapacita uložených stavů lze implementovat pomocí zásobníku objektů Memento - Může být paměťově i časově náročné, pokud jsou stavy příliš velké a kopírují se velká množství dat Je úzce svázané s typem objektu, jehož obsah si pamatuje Definice dvou různých rozhraní může být v některých jazycích složitá
Metody zpřístupněné Originatoru Implementace – friend (C++) class Originator { public: Memento* createMemento(); void setMemento(const Memento*); private: State* state_; }; class Memento { public: virtual ~Memento(); private: friend class Originator; Memento(); void setState(State*); State* getState(); private: State* state_; }; Memento využívá privátní metody, které zpřístupňuje Originatoru pomocí klíčového slova friend
Implementace – vnitřní třída (Java) class Originator { private String state; public Memento createMemento() { return new Memento(state); } public void setMemento(Memento memento) { state = memento.getState(); } public void doSomething(String data) { state = data; } public class Memento { private final String state; private Memento(String stateToSave) { state = stateToSave; } private String getState() { return state; } Memento je vnitřní třída Originatoru Originator má přístup k privátním položkám Mementa
Implementace – immutable memento (C#) public class Originator { public string State { get; set; } public Memento CreateMemento() { return new Memento(State); } public void SetMemento(Memento memento) { State = memento.Data; } public class Memento { public string Data { get; private set; } public Memento(string data) { Data = data; } Memento je čitelné veřejně, ale nelze jeho stav měnit
Implementace – serializace (C#) [Serializable] class Originator { private List state = new List (); public Memento CreateMemento() { Memento memento = new Memento(); return memento.SetState(state); } public void SetMemento(Memento memento) { state = (List ) memento.GetState(); } public void DoSomething(string s) { state.Add(s); Console.WriteLine(s); } [Serializable] class Memento { MemoryStream stream = new MemoryStream(); BinaryFormatter formatter = new BinaryFormatter(); public Memento SetState(object o) { formatter.Serialize(stream, o); return this; } public object GetState() { stream.Seek(0, SeekOrigin.Begin); object o = formatter.Deserialize(stream); stream.Close(); return o; } Serializace Zajistí správu privátních položek a vytváření hluboké kopie stavu
Implementace – přírůstková mementa Ukládání přírůstkových změn Memento obsahuje pouze změny vůči předcházejícímu stavu Originator musí dostat Mementa ve správném (obráceném) pořadí Můžeme kombinovat přírůstková Mementa s Mementy obsahujícími plný stav Line 17: + Lorem ipsum … Line 27–34: - A B C D E Line 2: + /n
Příklad – kalkulačka s historií Kalkulačka s funkcí zpět Uživatel chce mít možnost vrátit se zpátky ve výpočtu Ne všechny funkce jsou inverzní – je nutno použít Memento Zabrání opakování složitých výpočtů při chybě uživatele
Příklad – kalkulačka s historií Memento Stav Originatoru (hodnota na kalkulačce) Zabrání vytváření Mementa mimo Originator class CalculatorMemento { private: friend class Calculator; CalculatorMemento(); CalculatorMemento(const double value) : value_(value) { } void setState(const double value) { value_ = value; } double getState() const { return value_; } double value_; }; Zpřístupnění privátních položek Mementa Originatoru
Příklad – kalkulačka s historií Originator Originator se nestará o objekt Mementa class Calculator { public: Calculator() : value_(0) { } void execute(const Command command, const double value) { // perform the calculation } const CalculatorMemento* createMemento() { return new CalculatorMemento(value_); } void setMemento(const CalculatorMemento* m) { value_ = m->getState(); } private: int value_; };
Příklad – kalkulačka s historií Caretaker Uložení stavu před vykonáním příkazu Caretaker má zodpovědnost za objekt Mementa Zásobník pro jednotlivá Mementa a undo operace class CalculatorCaretaker { public: void execute(const Command command, const double value) { undoStack_.push(calculator_.createMemento()); calculator_.execute(command, value); } void undo() { if (!undoStack_.empty()) { const CalculatorMemento* m = undoStack_.top(); calculator_.setMemento(m); delete m; undoStack_.pop(); } private: std::stack undoStack_; Calculator calculator_; };
Příklad – piškvorky s ukládáním hry Desková hra s možností uložit a vrátit stav hry
Příklad – piškvorky s ukládáním hry Originator class Game { [Serializable] struct State { public char[,] board; public char currentPlayer; } private State state; // Methods for initialization and playing the game, displaying the board etc. public GameSave CreateSave() { GameSave memento = new GameSave(); return memento.SetState(state); } public void RestoreSave(GameSave memento) { state = (State) memento.GetState(); }
Příklad – piškvorky s ukládáním hry Memento a Caretaker [Serializable] class GameSave { MemoryStream stream = new MemoryStream(); static BinaryFormatter formatter = new BinaryFormatter(); public GameSave SetState(object o) { formatter.Serialize(stream, o); return this; } public object GetState() { stream.Seek(0, SeekOrigin.Begin); object o = formatter.Deserialize(stream); stream.Close(); return o; } class GameSaveCaretaker { public GameSave GameSave { get; set; } }
Shrnutí Kdy využít Memento Potřebujeme-li uložit stav objektu pro případný pozdější návrat Je-li nepřípustné porušit zapouzdření a uveřejnit detaily implementace zvenku Související návrhové vzory Command Může využívat Memento pro návrat operací Iterator Mementa mohou být použita pro iteraci