데이터를 마지막에 밀어 넣고, 마지막에 밀어 넣은 데이터를 먼저 꺼내는 후입 선출 방식의 자료구조이다. 스택은 언제나 가장 마지막에 밀어 넣은 최신 데이터를 먼저 취득한다. push와 pop 메서드를 사용하면 쉽게 구현할 수 있다.
const Stack = (function(){
function Stack(array = []){
this.array = array
}
Stack.prototype = {
constructor: Stack,
push(value){
return this.array.push(value)
},
pop(){
return this.array.pop()
}
}
return Stack;
}())
const stack = new Stack();
class Stack{
#array
constructor(array = []){
if(!Array.isArray(array)){
throw new TypeError('This is not an array.')
}
this.#array = array
}
push(value){
return this.#array.push(value)
}
pop(){
return this.#array.pop()
}
get array(){
return this.#array
}
}
class Stack{
#array
#index
constructor(array = []){
if(!Array.isArray(array)){
throw new TypeError('This is not an array.')
}
this.#array = array
this.#index = array.length
}
push(value){
this.#array[this.#index] = value
this.#index++
return this.#index
}
pop(){
const returnValue = this.#array[this.#index-1]
this.#array.length = --this.#index
return returnValue
}
get array(){
return this.#array
}
}
배열이 비어있을 경우를 깜빡하고 처리를 안해줘서 에러를 발생 시켰다. 그래서 조건문으로 배열이 0 이하일 경우를 처리해줬다.
RangeError: Invalid array length
class Stack{
#array
#index
constructor(array = []){
if(!Array.isArray(array)){
throw new TypeError('This is not an array.')
}
this.#array = array
this.#index = array.length
}
push(value){
if(value){
this.#array[this.#index] = value
this.#index++
return this.#index
}
return
}
pop(){
if(this.#index>0){
const returnValue = this.#array[this.#index-1]
this.#array.length = --this.#index
return returnValue
}
return
}
get array(){
return this.#array
}
}
Stack 클라스 함수 작성은 간단하니까 금방 했는데 막상 Undo, redo 동작을 구현하려니까 막-막...
이것 저것 뒤져보고 스택 구현 패턴까지 보다보니 그만하고싶어졌다;;
어떻게 짜야하는지 3일간 생각만하다가 일단 UI부터 때려박고 시작해서 어거지로 구현은 했다. 일단은 여기까지 만족하고 후퇴.
Undo function 구현 방법에는 두가지 디자인 패턴이 있다.
- 메멘토 패턴
- 커맨드 패턴
일단 간단하게 말하면 메멘토 패턴은 현재 상태를 캡쳐하고 저장하여 실행 취소를 구현하는 방법인데 커맨드 패턴에 비해서 구현이 쉽지만 메모리 관리 측면에서 비효율적이다. 하지만 난 쉽게가고 싶기때문에 메모리 따위는 신경쓰지 않는다.
디자이너니까 박스쉐도우정도는 넣어줬다.
소스 코드:
// Create elements
const $app = document.getElementById("app");
const $wrapper = document.createElement("div");
$wrapper.className = "wrapper";
const $canvas = document.createElement("div");
$canvas.className = "canvas";
$app.appendChild($wrapper);
$wrapper.appendChild($canvas);
//palette color arr
const paletteList = ["red", "blue", "green", "black"];
createPalette(paletteList);
// create button
const $buttonDiv = document.createElement("div");
$buttonDiv.className = "buttonDiv";
$wrapper.appendChild($buttonDiv);
const $undoButton = document.createElement("div");
$undoButton.className = "undo";
$undoButton.innerHTML = "Undo";
const $redoButton = document.createElement("div");
$redoButton.className = "redo";
$redoButton.innerHTML = "Redo";
$buttonDiv.appendChild($undoButton);
$buttonDiv.appendChild($redoButton);
// push palette function
function createPalette(arr) {
const $div = document.createElement("div");
$div.className = "palette";
$wrapper.appendChild($div);
//push list
arr.forEach((e) => {
const $list = document.createElement("div");
$list.className = "list";
$list.setAttribute("id", e);
$list.style.backgroundColor = e;
$div.appendChild($list);
});
}
// history object class
class Originator {
constructor(model) {
this._undoStack = [];
this._redoStack = [];
this._target = model;
}
take() {
const snapshot = this._target.snapshot();
this._undoStack.push(snapshot);
this._redoStack = [];
}
undo() {
if (this._undoStack.length) {
const snapshot = this._target.snapshot();
this._redoStack.push(snapshot);
const preData = this._undoStack.pop();
this._target.restore(preData);
}
}
redo() {
if (this._redoStack.length) {
const snapshot = this._target.snapshot();
this._undoStack.push(snapshot);
const preData = this._redoStack.pop();
this._target.restore(preData);
}
}
}
// target object class
class Model {
constructor(data) {
this.data = data;
}
snapshot() {
return this.data;
}
restore(data) {
this.data = data;
}
}
// instance
const canvas = new Model("white");
const originator = new Originator(canvas);
// $canvas.style.backgroundColor = canvas.data;
// add event listner to palette lists
const e = document.getElementsByClassName("list");
for (let i = 0; i < e.length; i++) {
e[i].addEventListener("click", () => {
// capture and reset model's data
originator.take();
canvas.restore(e[i].getAttribute("id"));
$canvas.style.backgroundColor = canvas.data;
});
}
// add event listner to undo button
$undoButton.addEventListener("click", () => {
originator.undo();
$canvas.style.backgroundColor = canvas.data;
});
// add event listner to redo button
$redoButton.addEventListener("click", () => {
originator.redo();
$canvas.style.backgroundColor = canvas.data;
});
참조: