생성 패턴 - 생성자 패턴, 프로토타입 패턴
생성 패턴(Creational Pattern)은 주어진 상황에 적합한 객체를 생성하는 방법에 중점을 둡니다. 기본적인 객체 생성 방식은 프로젝트의 복잡성을 증가시킬 수도 있기에, 생성 패턴은 이 과정을 제어하여 문제를 해결하는 것을 목표로 합니다.
디자인 패턴 중 첫번째로 공부하게 된 패턴은 생성 패턴 중 생성자(Constructor) 패턴이다. 생성자 패턴은 쉽게 말해 생성자를 사용하는 것, 즉 클래스로 인스턴스를 만드는 것을 의미한다고 생각하면 된다.
이 포스팅에서는 자바스크립트에서의 생성자 패턴을 더 깊게 이해하기 위해 ES6 이전의 객체 생성 문법부터 짚어봤다. (참고 도서: 윤인성님의 <(모던 웹을 위한)JavaScript jQuery 입문>)
※ 엄밀히 말해 자바스크립트의 Class 문법은 생성자 패턴(객체 초기화)과 프로토타입 패턴(메서드 메모리 공유)이 결합된 형태이다. 이 포스팅에서는 편의상 객체를 생성하는 관점에 집중하여 ‘생성자 패턴’으로 통칭하여 설명했다.
// 우리가 흔히 쓰는 class 문법
class Employee {
// 1. constructor: 생성자 패턴 역할 (데이터 초기화)
constructor(name) {
this.name = name;
}
// 2. 메서드: 프로토타입 패턴 역할 (자동으로 prototype에 저장됨)
printManual() {
console.log('출력!');
}
}

자바스크립트 클래스
1. 클래스 도입 배경
자바스크립트의 클래스는 모두 함수이다. 물론 그냥 함수는 아니고, 클래스의 가장 원형이 되는 것은 객체를 반환하는 함수이다.
// 1. 객체 반환 함수 (ES5)
function makeStudent(name, korean, english, math) {
var willReturn = {
name: name,
korean: korean,
english: english,
math: math,
getSum: function() {
return korean + english + math;
}
};
return willReturn;
}
이렇게 객체를 반환하는 함수를 이용해서 여러 객체를 만들어 사용했더니 문제가 생겼다. 바로 같은 메서드를 저장하는 메모리 공간이 계속 늘어난다는 점이였다. getSum을 저장하는 곳이 객체가 하나면 한 곳이였지만, 객체가 백 개면 백 곳이 되었다.
이 문제를 해결하기 위해 도입된 것이 바로 생성자 함수이다. 생성자 함수란 객체를 반환하는 함수로, 내부에 프로토타입(prototype)이라는 공용 공간(객체)을 가지고 있다. 즉 생성자 함수로 만들어진 객체는 메서드를 사용할 때 자신의 부모의 프로토타입에 저장된 메서드를 사용하게 되었다.

참고로 생성자 함수는 대문자로 시작하는 것이 관례이며, 생성자 함수로 객체를 만들 때는 반드시 new 키워드를 사용해야 한다.
// 2. 생성자 함수 방식 (ES5)
function Student(name, korean, english, math) {
this.name = name;
this.korean = korean;
this.english = english;
this.math = math;
}
Student.prototype.getSum = function() {
return this.korean + this.english + this.math;
};
const student = new Student('Daniel', 90, 100, 80);
console.log(student.getSum()); // 출력 270
그리고 ES6에서 이 생성자 함수를 더 편하게 쓸 수 있는 문법 설탕인 클래스 문법이 도입되었다.
// 3. 클래스 방식 (ES6)
class Student {
constructor(name, korean, english, math) {
this._name = name;
this._korean = korean;
this._english = english;
this._math = math;
}
get sum() {
return this._korean + this._english + this._math;
}
}
const student = new Student('Daniel', 90, 100, 80);
console.log(student.sum); // 270 (괄호 없이 속성처럼 접근)
이렇게 클래스를 사용하여 객체를 생성하는 것은 일종의 패턴이 되었고, 이를 생성자 패턴이라고 부르게 되었다.
2. 클래스 문법
기본 선언 및 생성자 구조

클래스는 함수와 달리 선언할 때 클래스명 뒤에 괄호를 붙이지 않는다. (괄호를 붙이면 Syntax Error가 발생하므로 유의) 클래스에서 객체를 만들 때는 생성자가 실행되며, 생성자에서 내부 변수의 초기 값을 지정한다. 메서드는 constructor와 같은 레벨에 함수로 정의한다. 여기서 정의한 함수는 프로토타입 내부에 저장된다.
변수 명명 관례 및 접근 제한자(#)

클래스 내부 변수를 만들 때의 관례는 외부에서 접근하지 않을 변수의 경우 변수명 앞에 _(언더바)를 붙이는 것이다. 만약 C언어의 private 처럼 절대 외부에서 접근하지 못하게 하려면, 접근제한자인 #을 변수명 앞에 붙이면 된다. 접근제한자를 붙인 변수는 생성자 밖에 선언해야한다.
class Student {
#address;
constructor(name, korean, english, math) {
this._name = name;
...
게터/세터 및 정적 메서드

게터와 세터의 경우, 각각 get, set 키워드를 사용하여 더 편리하게 쓸 수 있다. get, set 키워드를 쓸 때는 함수명에는 get과 set을 쓰지 않는 것이 관례이다. 또한 get 키워드를 사용해 정의한 게터는 함수가 아니라 속성처럼 동작하여, 호출할 때 괄호를 붙이지 않으니 유의하자.
class Student {
...
get name() {
return this._name;
}
set name(newName) {
this._name = newName;
}
}
const student = new Student('Daniel', 90, 100, 80);
console.log(student.name); // 출력 Daniel
static으로 선언한 변수나 메서드는 인스턴스를 만들지 않아도 클래스로 바로 접근이 가능하다. 도리어 인스턴스에서 static을 접근하면 에러가 난다.
클래스 상속

클래스의 상속은 extends 키워드로 가능하며, 부모의 생성자를 호출하고 싶은 경우 super()를 사용한다.
3. 클래스 메서드에서의 화살표 함수
클래스 내부에서 화살표 함수로 메서드를 정의하면, 부모의 프로토타입에 저장되지 않고 인스턴스 개별 본체에 각각 정의된다. 인스턴스를 만들 때마다 동일한 함수가 계속 생성되므로, 인스턴스가 아주 많아질 경우 메모리 효율은 일반 메서드보다 떨어진다. 즉, 화살표 함수를 쓰면 **프로토타입 패턴(공유)**을 포기하고, 순수한 **생성자 패턴(개별 소유)**으로 돌아간다
자습서의 7.2.2~7.2.3에서 class 내부의 toString()이 프로토타입에 저장되지 않는다고 하며 prototype 키워드를 사용해 저장하는 예제가 나오는데, 사실 둘 다 프로토타입 내부에 저장돼서 똑같은 결과물을 가질 것이다. 아마 프로토타입을 사용해서 메모리를 아낀다는 원리를 설명하다가 오류가 있었던 것 같다.