[Design Pattern] Memento Pattern
// memento.js
import { TodoList } from "./classes.js";
export const TodoHistory = {
history: [],
push(state) {
if (state) {
// always push a new Set to avoid reference issues
this.history.push(new Set([...state]));
}
},
pop() {
if (this.history.length > 1) {
this.history.pop();
return this.history.pop();
}
},
};
// Push new state into history when the list changes
const todoList = TodoList.getInstance();
todoList.addObserver(() => {
TodoHistory.push(todoList.items);
});
We store the whole list into history array, when store the list, we need to save the copy to avoid reference issue.
Observer mixin:
export const observerMixin = {
observers: new Set(),
addObserver(obs) {
this.observers.add(obs);
},
removeObserver(obs) {
this.observers.delete(obs);
},
notify() {
this.observers.forEach((obs) => obs());
},
};
TodoList:
import { observerMixin } from "./mixin.js";
export class TodoItem {
constructor(text) {
this.text = text;
}
equals(other) {
return this.text === other.text;
}
}
class TodoList {
#data = new Set();
get items() {
return this.#data;
}
/**
* Singleton instance
*/
static instance = null;
static {
this.instance = new TodoList();
}
static getInstance() {
return this.instance;
}
constructor() {
if (TodoList.instance) {
throw new Error("Use TodoList.getInstance() to access the list");
}
}
// behavior
add(item) {
const array = Array.from(this.#data);
const todoExists = array.filter((t) => t.equals(item)).length > 0;
if (!todoExists) {
this.#data.add(item);
this.notify();
}
}
delete(text) {
const array = Array.from(this.#data);
const todoToDelete = array.filter((t) => t.text === text)[0];
if (todoToDelete) {
this.#data.delete(todoToDelete);
this.notify();
}
}
find(text) {
const array = Array.from(this.#data);
return array.find((t) => t.text === text);
}
replaceList(list) {
this.#data = list;
this.notify();
}
}
/**Mixin */
Object.assign(TodoList.prototype, observerMixin);
export { TodoList };