Object Literal
Class
와 Prototype
Factory Pattern
Singleton
우리는 클래스를 만들고, 이 클래스를 통해 여러 객체들을 만들어낼 수 있어요.
class Person{
}
const junzzi = new Person();
const lokba = new Person();
하지만 우리가 흔히 알고있길 class를 선언
하지 않고도 object literal
로도 객체들을 만들 수 있어요. 그렇다면 우리가 클래스를 선언하여 객체를 만들땐 적절한 다른 이유
가 있어야겠죠?!
오늘 시간엔 그 점을 탐구해보려고해요!!
✅ Object Literal로 만드는 객체
를 Object
✅ Class로 만드는 객체
를 Class Object
로 명명할게요.
이번 포스팅에선 이 둘의 tradeoff
에 대해 간단히 고찰해보려고 해요. 😱 TL:DR주의
Object
가 갖는 특징다음과 같이 객체 리터럴을 활용하여 만들 수 있어요!
const obj = {
data: 10,
method_one() {
console.log("method_one");
},
};
단일 인스턴스이다.
private
화 하기 힘들다.
초기화 할 수 없다.
단일 인스턴스의 경우 우리가 잃어야하는 것은?
단일 인스턴스 (싱글톤)이기 때문에 여러 인스턴스로 만들어낼 수 없어요. 그렇기 때문에 다음과 같은 코드는
const obj = {
data: 10,
method_one() {
console.log("method_one");
},
};
const clone = obj;
obj.data = 20;
console.log(clone.data); // 20
하나의 저장소를 여러 클론된 식별자로 접근할 뿐이네요?! 그렇기 때문에 여러 인스턴스화를 할 수 없게 됩니다. 하나의 인스턴스를 여러 식별자들이 가리키고 있을 뿐이죠.
private화 하기 힘들다
불가능하다
가 아닌 힘들다
입니다. 우선 우리는 모든 정보가 공개된 객체를 만들고 싶지 않을것이에요. 중요한 정보는 감춰줄 수 있어야죠.
굳이 Object
에 private한 멤버를 추가하고자 한다면 다음과 같이 코드를 작성해야할 것입니다.
private
화 하지 않는다면, data
프로퍼티를 어디서든 접근하게 될 것이에요.
const obj = {
data: 10,
method_one() {
console.log("hi");
},
};
console.log(obj.data); // ❌
이를 바라지 않는다면 다음과 같이 숨겨둘 수 있을 것 같아요.
const obj = {
private_members: (function () {
let data = 10;
return {
getData() {
return data;
},
setData(newData) {
data = newData;
},
};
})(),
method_one() {
console.log("hi");
},
};
console.log(obj.private_members.getData());
console.log(obj.private_members.setData(20));
console.log(obj.private_members.getData());
즉시 실행함수와 클로저를 이용하여 구현할 수 있으나, 매우 불편한 것 같아요.
(더 좋은 방법은 생각나지 않아요. 있다면 공유해주세요!!)
not construtor
클래스 객체의 경우 인스턴스를 생성할 때, constructor
를 호출하게 되기에 constructor
내부에 로직을 추가하면 초기화할 수 있어요.
하지만 Object
의 경우 따로 init
함수를 만들지 않는 한 초기화할 수 없어요.
정리하자면
하나의 인스턴스밖에 못만들기 때문에 확장이 불편할 것 같아요. private
한 멤버를 갖기에도 들어가는 비용이 큰 것 같구요. 하지만, 단 하나의 객체만을 필요로 한다면 굳이 클래스를 작성하지 않고 literal
표기법으로 생성하면 될 것 같아요. (싱글톤)
Class Object
가 갖는 특징클래스로 객체를 만든다는 것은 자바스크립트의 Prototype
을 활용하여 객체를 만든다는 것과 동일하다고 볼 수 있어요. (완전히 같지 않습니다.)
class Class_Object {
constructor() {
this.data = 10;
}
method_one() {
console.log("method_one");
}
}
const CO = new Class_Object();
여러 개의 인스턴스를 만들 수 있어요.
객체 없이도 접근할 수 있는 static
멤버, 메소드들을 만들 수 있어요.
멤버를 쉽게 private
화 할 수 있어요.
1. 여러 개의 인스턴스를 만들 수 있다는 것은?
하나의 클래스가 정의되어 있고, 이 클래스를 템플릿 삼아 객체를 찍어내게되면, 모든 인스턴스 멤버와 메소드가 각 객체 별로 생기게 되요.
그렇게 된다면, 다음 코드는 이렇게 실행될 것입니당
class Class_Object {
constructor() {
this.data = 10;
}
method_one() {
console.log("method_one");
}
}
const CO = new Class_Object();
const CO1 = new Class_Object();
CO.data = 20;
console.log(CO.data); // 20
console.log(CO1.data); // 10
결국 여러 개의 인스턴스
는 각자의 상태를 갖게되고, 이러한 상태 구조가 필요한 경우가 있을 것 같아요 !
2. 객체 없이도 접근할 수 있는 static
멤버, 메소드들을 만들 수 있다는 것은?
class Class_Object {
static static_data = 20;
constructor() {
this.data = 10;
}
method_one() {
console.log("method_one");
}
static method_static_one() {
console.log("method_static_one");
}
}
console.log(Class_Object.static_data);
Class_Object.method_static_one();
클래스 내부에서 공통으로 쓰이거나, 외부에서 객체 생성 없이도 접근할 수 있는 데이터, 메소드가 필요하다면 static
키워드를 통해 정적인 메소드를 만들어 낼 수 있어요!! (언제 static
메소드가 필요한지는 다음절에서 자세하게 다룰 예정입니다)
3. 쉽게 멤버와 메소드를 private화 할 수 있어요
class Class_Object {
#private_data = 30;
static static_data = 20;
constructor() {
this.data = 10;
}
method_one() {
console.log("method_one");
}
static method_static_one() {
console.log("method_static_one");
}
#init() {
console.log("init method");
}
}
const newObj = new Class_Object();
newObj.#init();
console.log(newObj.#private_data);
Object
에서 private
화 하는 것보다 훨씬 간단 명료하네요 ?!
정리하자면
하나의 저장소 객체만을 필요로 한다면, Object
방식을 사용하면 되지만, 1️⃣ 여러개의 인스턴스를 같은 템플릿으로 만들어야하고, 2️⃣ 객체 생성 없이도 접근할 수 있는 메소드가 필요하고, 3️⃣ 쉽게 private한 멤버와 메소드를 갖고 싶다면, 클래스를 선언해두고 객체를 생성하면 되겠네요 !!
제가 생각하는 static
사용의 이유는
1. 객체의 생성 없이도 사용할 수 있는 범용적인 메소드의 경우
2. 팩토리 패턴의 팩토리 메소드 같이 여러 객체를 만드는 상황에서 관심사를 분리할 수 있다
class Calculator {
static add(a,b){
return a+b;
}
}
console.log(Calculator.add(10,20)) // 30
위와 같이 add
라는 함수가 객체에 의해서만 동작하지 않는 경우라면 static
메소드를 작성해볼 수 있을 것 같아요.
또 모든 객체가 다음과 같이 같은 값을 공유한다면?
class UtecoCrew{
static sameAttribute = 'Person'
}
const junzzi = new UtecoCrew();
const lokba = new UtecoCrew();
모든 크루는 사람이기 때문에 sameAttribute
값은 person
일 것입니다. 이렇게 모든 객체가 공통으로 갖는 값이 있다면 static
으로 정의할 수 있겠군요.
팩토리 패턴과 팩토리 메소드가 궁금하다면? 요기를 참고해봅시다
간단하게 말씀드리자면, 여러개의 확장된 객체를 만들고자 할때 사용할 수 있습니다.
다음은 팩토리 메소드 패턴을 활용하지 않은 경우의 코드입니다.
class Car{
constructor(type){
switch(type){
case 'blue':
return new BlueCar();
case 'red':
return new RedCar();
}
}
}
위와 같이 작성해줄 수 있지만, 그렇다면 Car 클래스 생성자의 코드가 길어지겠죠? 이를 팩토리 메서드 패턴
을 통해 관심사의 분리가 된 코드로 작성해보자면?
class Car{
static createCar(type){
switch(type){
case 'blue':
return new BlueCar();
case 'red':
return new RedCar();
}
}
위와 같이 만들어 색깔 별 자동차를 만드는 코드를 확실히 분리할 수 있겠군요 !!
위와 같이 1. 하나의 클래스 속성을 가진 여러 타입의 객체를 만들어 낼 때 관심사를 분리하기위해 혹은 2. 객체의 생성없이도 사용할 수 있는 메소드를 만들기 위해 혹은 3. 공통된 속성 값을 유지하는 용도로 static
키워드를 활용해볼 수 있을 것 같아요.
하지만 많으면 여러모로 안좋다고 합니다. 그 부분은 이 곳에서 확인해주세요 ! 정적 메소드 써도될까?
객체 지향에서 멀어질 수 있나봐요. 클래스 자체에 심어져 있는 놈들이기 때문에, 객체의 생성과는 상관없이 공간을 차지하게 되어버리는 거죠. 많으면 많을 수록 전역 변수, 전역 메소드를 많이 만드는 느낌이고, 1학년 때 배웠던 C와 다를게 없어질 것 같아요.
가비지 컬렉터라는 말을 아시나요? 쉽게 생각하면 사용하지 않는 동적 할당된 메모리를 청소해주는 개체인데요. 정적 메소드는 동적으로 할당되는 영역이 아니기 때문에 청소해주지 못하게되요. 그 클래스의 정적 메소드가 사용되지 않더라도 말이죠.