Pemrograman · Pola desain
Penjelasan pola Memento beserta contohnya
Jawaban singkat
Memento adalah pola desain behavioral yang menangkap state internal sebuah objek dalam objek snapshot, yang disebut memento, sehingga objek itu bisa dipulihkan ke state tersebut nanti. Objek pemilik state (originator) membuat dan memulihkan memento, sedangkan objek lain (caretaker) hanya menyimpannya dan tidak pernah melihat isinya. Ini cara standar untuk mengimplementasikan undo, checkpoint, dan save game tanpa merusak enkapsulasi.
Tujuan dan masalah yang diselesaikan
Tujuan: tanpa melanggar enkapsulasi, tangkap dan eksternalisasikan state internal objek sehingga objek itu dapat dipulihkan ke state tersebut nanti. Memento adalah salah satu pola behavioral dalam buku Gang of Four (GoF).
Bayangkan aplikasi menggambar di mana sebuah bentuk punya posisi, ukuran, warna, dan daftar nilai turunan privat yang dipakai untuk rendering. Anda ingin tombol undo. Undo manager bisa menyalin field bentuk itu sendiri, tetapi ia harus tahu setiap field privat, dan setiap kali class bentuk berubah, undo manager rusak. Menjadikan semua field publik hanya demi undo akan membuat kode mana pun bisa menaruh bentuk itu dalam keadaan tidak valid.
Memento menyelesaikan ini dengan membiarkan bentuk itu sendiri menghasilkan snapshot. Hanya bentuk itu yang tahu apa isi snapshot dan cara memulihkan darinya. Undo manager memperlakukan snapshot sebagai objek opaque: ia menyimpannya dalam daftar dan mengembalikan satu saat pengguna menekan undo.
Struktur dan peserta
| Peran | Tanggung jawab |
|---|---|
| Originator | Objek yang state-nya disimpan; membuat memento dari state saat ini dan memulihkan dirinya dari sebuah memento |
| Memento | Snapshot immutable dari state originator; tidak mengekspos apa pun yang berguna bagi objek lain |
| Caretaker | Memutuskan kapan menyimpan dan kapan memulihkan, serta menyimpan memento (misalnya dalam stack), tetapi tidak pernah membaca atau mengubah isinya |
Gagasan kuncinya adalah dua interface: yang lebar untuk originator, yang bisa membaca state tersimpan, dan yang sempit untuk semua pihak lain, yang hanya bisa memegang memento.
Implementasi TypeScript
Class memento memakai private field JavaScript (#state), sehingga caretaker tidak bisa membaca data tersimpan dari objek memento. Pembacaan lewat method static EditorMemento.read, yang hanya dipanggil editor. Di proyek nyata, taruh class memento di modul yang sama dengan editor dan jangan di-export, sehingga modul lain bisa memegang memento tetapi tidak pernah membukanya.
type EditorState = { text: string; cursor: number };
class EditorMemento {
readonly #state: EditorState;
constructor(state: EditorState) {
this.#state = { ...state }; // copy, so later edits do not change the snapshot
}
static read(m: EditorMemento): EditorState {
return { ...m.#state };
}
}
class Editor {
private state: EditorState = { text: "", cursor: 0 };
type(words: string) {
const text = this.state.text + words;
this.state = { text, cursor: text.length };
}
get text() { return this.state.text; }
save(): EditorMemento { return new EditorMemento(this.state); }
restore(m: EditorMemento) { this.state = EditorMemento.read(m); }
}
class History {
private stack: EditorMemento[] = [];
constructor(private editor: Editor) {}
backup() { this.stack.push(this.editor.save()); }
undo() { const m = this.stack.pop(); if (m) this.editor.restore(m); }
}
const editor = new Editor();
const history = new History(editor);
history.backup(); editor.type("Hello");
history.backup(); editor.type(" world");
console.log(editor.text); // Hello world
history.undo();
console.log(editor.text); // Hello
history.undo();
console.log(JSON.stringify(editor.text)); // ""History adalah caretaker. Ia memanggil backup() sebelum setiap perubahan dan undo() untuk kembali, tetapi tidak pernah melihat text atau cursor. Jika nanti Anda menambahkan rentang seleksi ke EditorState, History tidak berubah.
Memento vs Command untuk undo
Ada dua cara umum mengimplementasikan undo, dan keduanya sering digabung:
| Pendekatan | Yang disimpan | Cocok bila | Biaya |
|---|---|---|---|
| Memento | Snapshot seluruh state sebelum setiap perubahan | Perubahan sulit dibalik, atau state kecil | Memori bertambah sebesar ukuran state dikali jumlah langkah |
| Command | Operasi dan data yang cukup untuk membaliknya | Setiap operasi punya kebalikan yang sederhana | Setiap command butuh logika undo yang benar |
Kompromi praktis: simpan command untuk sebagian besar langkah dan memento penuh setiap N langkah, atau simpan hanya bagian state yang diubah sebuah command.
Kapan dipakai dan kapan tidak
- Pakai untuk undo, redo, checkpoint, save game, dan tombol "batal" pada form yang harus membatalkan edit.
- Pakai untuk transaksi di memori: ambil snapshot sebelum operasi berisiko dan pulihkan bila gagal.
- Pakai bila caretaker tidak boleh bergantung pada field internal originator.
- Jangan dipakai bila state sangat besar dan sering berubah, kecuali Anda membatasi panjang riwayat atau hanya menyimpan perbedaannya.
- Jangan memakai seluruh seremoninya bila state sudah berupa nilai immutable biasa. Dengan data immutable, nilai lama sudah merupakan snapshot, dan menyimpan daftar nilai sebelumnya sudah cukup.
Penggunaan di dunia nyata
- History API browser:
history.pushState(state, "", url)menyimpan objek state untuk setiap entri riwayat, dan eventpopstatemengembalikannya saat pengguna bernavigasi. - Redux dan store serupa menyimpan snapshot state yang immutable, yang dipakai tool untuk time-travel debugging.
- Database SQL mendukung
SAVEPOINTdi dalam transaksi, sehingga pekerjaan bisa di-rollback ke titik bernama. - Editor teks, program grafis, dan game menyimpan snapshot untuk undo, autosave, dan slot simpan.
Pola terkait
| Pola | Hubungannya dengan Memento |
|---|---|
| Command | Command bisa melakukan undo dengan membalik operasi; ia juga bisa menyimpan memento yang diambil sebelum berjalan dan memulihkannya di undo. |
| Prototype | Prototype mengkloning seluruh objek untuk membuat yang baru; memento menyimpan state untuk memulihkan objek yang sama nanti. |
| Iterator | Iterator dapat mengekspos posisinya saat ini sebagai memento sehingga traversal dapat dilanjutkan nanti. |
| State | State mengubah perilaku objek saat state-nya berubah; Memento hanya mencatat dan memulihkan data. |
FAQ
Apa perbedaan antara originator dan caretaker?
Originator memiliki state dan satu-satunya objek yang dapat membuat memento dan memulihkan darinya. Caretaker memutuskan kapan menyimpan dan memulihkan serta menyimpan memento, tetapi memperlakukannya sebagai opaque.
Apakah Memento merusak enkapsulasi?
Tidak, justru itu tujuannya. Field internal originator tetap privat, dan memento menyembunyikannya dari caretaker, sehingga hanya originator yang dapat membaca state tersimpan.
Bagaimana membatasi pemakaian memori dengan Memento?
Simpan jumlah snapshot maksimum dan buang yang paling lama, simpan hanya bagian state yang berubah, atau gabungkan memento dengan command yang mencatat perubahan kecil.
Apakah serialisasi JSON itu memento?
Serialisasi state ke JSON bisa menjadi format penyimpanan sebuah memento, dan berguna untuk menyimpan ke disk. Pola itu sendiri berkaitan dengan siapa yang membuat, menyimpan, dan memulihkan snapshot, bukan formatnya.