안녕하세요. 이번 시간에는 자바스크립트 디자인 패턴에 대해서 알아보겠습니다. 갑자기 웬 디자인이냐고요? 디자인 대신 패턴이라고 하면 좀 더 이해하기 쉽습니다. 자바스크립트 앱을 만들 때 자주 사용되는 패턴입니다. 이후로 나오는 패턴들을 잘 숙지하고 있다가 상황에 맞춰 사용하면 됩니다.
첫 번째는 예전에 배웠던 모듈 패턴입니다. 잊어버렸다면 링크를 참고하세요! IIFE가 바로 비공개 변수를 만드는 디자인 패턴 중 하나인거죠. 비공개 변수를 가질 수 있다는 게 모듈 패턴의 특징이라고 했죠?
다음은 싱글턴 패턴입니다. 싱글턴의 싱글은 혼자의 싱글이 맞습니다. 객체를 만들 때, 하나의 생성자로 여러 객체를 만들 수 있었습니다. 하지만 싱글턴은 필요에 의해 단 하나의 객체만을 만들 때 사용합니다. 아래 처럼요.
var obj = {
a: 'hello',
b: function() {
alert(this.a);
}
};
?? 엄청 간단하네요. 사실 객체 리터럴이 바로 싱글턴 패턴의 대표적인 예입니다. 저 객체는 단 하나밖에 존재하지 않죠. 하지만 모든 속성이 다 공개되어 있다는 단점이 있습니다. 비공개로 만드는 게 바로 제대로 된 싱글턴입니다.
var singleton = (function() {
var instance;
var a = 'hello';
function initiate() {
return {
a: a,
b: function() {
alert(a);
}
};
}
return {
getInstance: function() {
if (!instance) {
instance = initiate();
}
return instance;
}
}
})();
var first = singleton.getInstance();
var second = singleton.getInstance();
console.log(first === second); // true;
코드가 확 길어졌네요. 차근히 살펴보면 쉽습니다. IIFE로 비공개 변수를 가질 수 있게 만들어줍니다. 그리고 그 안에 instance변수와 initiate 함수를 만들어줍니다. initiate 함수 안의 내용이 실제 객체의 내용입니다. 위의 obj 객체와 비교하면 a가 비공개 변수가 되었네요.
IIFE로 즉시 반환되는 부분(return)을 보시죠. getInstance라는 메소드를 가진 객체를 반환하는데, getInstance 함수를 호출하는 순간 내부적으로 initiate 함수가 호출되고, instance에 아까 그 객체의 내용이 저장되고 동시에 반환됩니다. getInstance가 여러 번 호출됐을 경우에는 코드를 보시면 이미 instance 객체가 있는 경우에는 initiate를 거치지 않고 바로 반환하는 것을 알 수 있습니다.
first와 second 변수를 보면 두 번 다 getInstance 함수를 호출했는데요. 결과적으로 두 변수는 같습니다. first 때 initiate된 객체를 second 때도 똑같이 반환받았기 때문이죠. 즉, 아무리 호출해도 기존에 있던 객체는 복사되는 것도 아니고 그냥 그대로 반환됩니다. 싱글턴 패턴은 모듈 패턴을 변형한 디자인 패턴입니다.
싱글턴을 언제 써야할 지 잘 모르겠나요? 처음 네임스페이스를 만들 때 사용하면 됩니다! 예를 들어 게임을 만든다고 치면, 게임 객체를 싱글턴으로 만드는 겁니다. 게임 내의 모든 것을 감싸고 있는 객체를 말이죠. 게임을 실행했을 때 게임은 한 번만 켜져야하기 때문에 싱글턴이 적절합니다.
세 번째는 생성자 패턴입니다. 이것도 이미 배웠죠? 상속을 배울 때 다뤘습니다! 대부분의 객체는 이 패턴으로 만들게 됩니다. 특히 상속이 필요할 때는 제일 많이 쓰이죠. 모듈 패턴과 생성자 패턴을 조합해서 코드를 보기 좋게 만들 수 있습니다.
function Vehicle(name, speed) {
this.name = name; this.speed = speed;
}
Vehicle.prototype.drive = function () {
console.log(this.name + ' runs at ' + this.speed)
};
function 부분과 prototype 부분으로 따로 떨어져 있는 이 코드를 하나로 묶어줍시다.
var Vehicle = (function() {
function Vehicle(name, speed) {
this.name = name; this.speed = speed;
}
Vehicle.prototype.drive = function () {
console.log(this.name + ' runs at ' + this.speed);
};
return Vehicle;
})();
생성자와 프로토타입을 모두 Vehicle 변수 안에 넣었습니다. 변수 Vehicle과 생성자 Vehicle 이름이 같아서 걱정하시는 분이 있을 수도 있는데 IIFE라서 바로 변수 Vehicle에 생성자 Vehicle이 덮어씌워집니다.
이외에도 빌더 패턴, 팩토리 패턴, 중재자 패턴, 옵저버 패턴, 메소드 체이닝 패턴 등이 있는데 고급 강좌에서 다뤄보겠습니다. (메소드 체이닝 패턴은 자주 사용됩니다. 28강 턴제 게임 만들기에서 알려드리겠습니다!)
다음 시간에는 자바스크립트 함수형 프로그래밍에 대해 알아보겠습니다.
다름이아니라 생성자, 모듈 패턴 연구를 해봤어요.
class 키워드를 사용하면서 private 필드를 getInstance() 메서드 안에서 만들려는 시도를 했었습니다.
(java 스럽게 쓰면 어떨까 하는 목적에서 시작했습니다)
문제는 이것이 class Singleton 과 getInstance() 반환값을 Object 로 하다보니
instanceof Singleton 비교에서 false 가 나오기때문에
{__proto__: this | Singleton } 을 해야 햇습니다.
그런데 테스트 코드에서 예상밖에 오류가 나오는데요..
오류는 이렇습니다.
-> "Function.prototype.toString requires that 'this' be a Function"
toString 이 없어서 나는 오류같은데..
생성된 인스턴스를 typeof 찍어보면 'function' 이라고 잘나오는데,
이걸 해결할 방법이 있을까요?
class Singleton {
constructor() {
throw new Error(this.__proto__.constructor.name +
'()는 생성자 인스턴스 생성이 불가능 합니다.');
}
static getInstance() {
return (() => {
const theInstance = {
__proto__: this // class-sync
};
Object.defineProperty(theInstance, '__proto__',
{enumerable: false});
Object.freeze(theInstance);// cannot-be-modified
return theInstance;
})();
}
}
// 태스트코드
//const singleton = new Singleton();// 애러
console.log(`typeof Singleton = ${typeof Singleton}`);
const s = Singleton.getInstance();
console.log(`Singleton.getInstance() = ${s}`);
class Singleton {
static #instance;
constructor() {
if (!Singleton.#instance) {
Singleton.#instance = this
}
return Singleton.#instance
}
get() {
return Singleton.#instance;
}
static get() {
return this.#instance;
}
}
const a = new Singleton();
const b = new Singleton();
console.log(a.get() === b.get()); // true
console.log(a.get() === Singleton.get()); // true
console.log(Singleton.get() instanceof Singleton); // true