게시글

5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭
강좌26 - JavaScript - 8년 전 등록

디자인 패턴(싱글턴, 모듈, 생성자)

안녕하세요. 이번 시간에는 자바스크립트 디자인 패턴에 대해서 알아보겠습니다. 갑자기 웬 디자인이냐고요? 디자인 대신 패턴이라고 하면 좀 더 이해하기 쉽습니다. 자바스크립트 앱을 만들 때 자주 사용되는 패턴입니다. 이후로 나오는 패턴들을 잘 숙지하고 있다가 상황에 맞춰 사용하면 됩니다.

첫 번째는 예전에 배웠던 모듈 패턴입니다. 잊어버렸다면 링크를 참고하세요! 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강 턴제 게임 만들기에서 알려드리겠습니다!)

다음 시간에는 자바스크립트 함수형 프로그래밍에 대해 알아보겠습니다.

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.
5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭

댓글

10개의 댓글이 있습니다.
4년 전
안녕하세요^^ 유튭에도 간간히 덧글남긴 Truestar 입니다.
다름이아니라 생성자, 모듈 패턴 연구를 해봤어요.
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}`);
6달 전
매우 늦었지만 혹시나 나중에 보실 분들을 위해 답변을 남기자면 다음과 같이 하면 됩니다.
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
5년 전
삭제가 안되서 다시 댓글을 남깁니다. singleton.getInstance().a로 instance로 받은 객체들에 접근할 수 있다는 것을 알았는데 한번 다른 값으로 수정하게 되면 원래 singleton에 있는 변수인 a에는 접근할 방법이 없는건가요?
5년 전
그 때도 singleton.getInstance().a 하시면 됩니다. 어떻게 수정하셨나요?
5년 전
싱글턴 패턴을 할 때 singleton.getInstance().a 이런식으로 a에 접근이 가능한데 왜 그런거죠? 접근이 안되야 하는거 아닌가요?
5년 전
모듈패턴의 drive에 console.log는 어떻게 확인할수 있는건가요?
5년 전
new Vehicle().drive()하시면 됩니다.
6년 전
let singleton = (function() {
let a = 'hello';
return {
getInstance: function () {
return {
a: a,
b: function() {
alert(a);
}
};
}
}
})();
var first = singleton.getInstance();
var second = singleton.getInstance();
console.log(first === second);
이렇게 써도 값나오는 건 똑같아 보이는데 왜 false라고 뜰까요?
6년 전
getInstance의 return에서 {} 객체를 새로 만들어서 리턴하기 때문입니다.
6년 전
안녕하세요 class로 싱글턴은 어떻게 구현할 수 있을까요?
6년 전
return {
a: a,
b: function() {
alert(a);
}
}; 대신에 return new A() 이런 식으로 하시면 됩니다.
6년 전
안녕하세요 선생님.
보다보니 공금한게 있어서요.
singleton을 IIFE로 감싸지 않으면 동작하지 않던데,
그 이유가 무언가요?
6년 전
IIFE를 쓰는 목적은 바로 객체를 리턴해주기 위해서입니다. IIFE를 통해서 singleton이라는 객체를 리턴해주어야하고, IIFE로 감싸지 않으면 함수를 리턴해버립니다.
6년 전
감사합니다. 이해가 되었습니다. 코딩할 때 유용하게 씨겠습니다.
7년 전
싱글턴패턴을 namespace를 만들때 쓴다고 하였는데, 언뜻 이해가 되지 않습니다. 그에대한 사용예는 뭐가 있을까요? 제가 c#에서 넘어온 케이스라 보통 namespace라 하면 scope 이 먼저 떠오르고, 어떤 변수나 멤버들을 부를때, MyNamespace.SomeObject.someMethod() 이런식으로 구분을 하여 부르는데, 이런 개념과 매치가 잘 되지 않습니다.
7년 전
네 맞습니다. MyNamespace같은 것을 만드는 겁니다. 전역 스코프를 무분별한 변수로 오염시키는 대신에요. 전역 스코프에 변수가 많아지면 라이브러리간 변수 충돌이 발생할 수도 있어 네임스페이스를 만들어 충돌을 방지합니다. 물론 js에서는 네임스페이스도 일반 객체일 뿐입니다.
7년 전
생성자패턴을 쓰는 이유가 무엇인가요? IIFE 로 감싼것과 아닌것과 동작하는것은 똑같은것 같은데요
7년 전
코드를 그룹화하는 겁니다. 사실 안 해도 되는데 그냥 보기 좋게 묶는 겁니다. ㅎㅎ
7년 전
안녕하세요 좋은 자료 제공해주셔서 잘 공부하고 있습니다!
다름이 아니라 마지막의 생성자 패턴으로 만든 Vehicle이라는 변수는 어떻게 이용하는지 알려주실 수 있을까요
Vehicle이라는 변수를 호출해도 계속 undefined로만 뜨네요 ㅠ
7년 전
new Vehicle('붕붕이', 100)처럼 new로 호출하시면 됩니다.