1. 객체 공장 만들기
청소부들의 정보가 담겨 있는 객체를 만들어 데이터를 관리한다고 생각해 보자.
두 명의 청소부가 있을 때 다음과 같이 객체를 만들 수 있을 것이다.
let cleaner1 = {
name: 'Jin',
department: 'lobby',
clean: function() {
return `Jin has cleaned the lobby.`
}
}
let cleaner2 = {
name: 'Kim',
department: 'bedroom',
clean: function() {
return `Kim has cleaned the bedroom.`
}
}
그렇지만 청소부가 수십 명, 수백 명이 된다면 어떨까?
담아야 할 정보의 개수(프로퍼티의 개수)가 수십개로 늘어난다면 어떨까?
매번 객체들을 손으로 일일히 작성해야 할 것이다.
이 객체들은 전부 똑같은 키를 가지고 있는데, 이것을 한번에 찍어낼 수 있는 공장을 만들 수 없을까?
클래스가 바로 그런 기능을 수행한다.
class Cleaner {
constructor(name, department) {
this.name = name;
this.department = department;
this.clean = function () {
return `${this.name} has cleaned the ${this.department}.`;
};
}
}
Cleaner 클래스는 앞으로 만들어질 청소부 객체를 만들어 내는 공장이다.
이 공장에서 청소부 객체를 만들어 낼 때는 new 키워드를 사용한다.
let cleaner1 = new Cleaner("Jin", "lobby");
let cleaner2 = new Cleaner("Kim", "bedroom");
console.log(cleaner1.clean()); //"Jin has cleaned the lobby."
console.log(cleaner2.clean()); //"Kim has cleaned the bedroom."
이렇게 클래스를 이용해 만들어진 만들어진 각각의 객체들은 인스턴스라고 부른다.
그런데 클래스 문법은 ECMAScript 6에서 새롭게 도입된 문법이다.
ECMAScript 5 문법으로는 이와 거의 똑같은 기능을 생성자 함수를 통해서 구현할 수 있다.
function Cleaner(name, department) {
this.name = name;
this.department = department;
this.clean = function () {
return `${this.name} has cleaned the ${this.department}.`;
};
}
생성자 함수를 이용해 인스턴스를 만들 때도 동일하게 new 키워드를 사용한다.
let cleaner1 = new Cleaner("Jin", "lobby");
let cleaner2 = new Cleaner("Kim", "bedroom");
console.log(cleaner1.clean()); //"Jin has cleaned the lobby."
console.log(cleaner2.clean()); //"Kim has cleaned the bedroom."
2. 프로토타입의 필요성
이렇게 클래스를 이용해 객체를 찍어내면 객체를 일일이 생성하는 수고를 획기적으로 줄일 수 있다.
그렇지만 여전히 효율을 더 높일 수 있는 여지는 남아 있다.
지금 정의된 Cleaner 클래스로 인스턴스를 만들면 각각의 인스턴스에 속한 새로운 clean 함수가 생성된다.
Clean 클래스를 통해서 만들어낸 cleaner1과 cleaner2 인스턴스를 확인해 보면 위와 같은 결과가 나온다.
name과 department는 각각 다른 값을 지니기 때문에 인스턴스마다 새롭게 주어져야 한다 해도,
clean이라는 함수는 매번 새롭게 정의할 필요 없이 하나만 만들어 두고 각 인스턴스들이 가져다 쓸 수 없을까?
여기서 프로토타입의 필요성이 생겨난다.
위의 Cleaner 클래스를 다음과 같이 바꿔 보자.
class Cleaner {
constructor(name, department) {
this.name = name;
this.department = department;
}
clean() {
return `${this.name} has cleaned the ${this.department}.`;
}
}
이제 clean 함수는 Cleaner.prototype에 정의된 메서드가 되었다.
이 상태에서 cleaner1과 cleaner2 인스턴스를 다시 확인해 보면 다음과 같이 나온다.
이제 clean 메서드는 인스턴스의 개별 속성으로 존재하지 않지만, 전과 똑같은 방식으로 사용할 수 있다.
각각의 인스턴스가 가진 __proto__ 속성이 Cleaner의 프로토타입을 가리키고 있고,
clean 메서드를 실행하면 이 프로토타입에서 메서드를 가져다 쓰게 된다.
생성자 함수로 같은 기능을 구현하기 위해서는 다음과 같이 작성하면 된다.
function Cleaner(name, department) {
this.name = name;
this.department = department;
}
Cleaner.prototype.clean = function () {
return `${this.name} has cleaned the ${this.department}.`;
};
이렇게 인스턴스와 클래스는 프로토타입을 매개로 서로 관계를 맺고 있다.
클래스의 prototype은 클래스의 프로토타입을 가리키고, 프로토타입의 constructor는 클래스를 가리킨다.
인스턴스의 __proto__는 인스턴스를 만들어 낸 클래스의 프로토타입을 가리킨다.
3. 객체 지향 프로그래밍
위에서 작성한 Cleaner 클래스는 데이터와 이를 처리하는 메서드를 모두 만들도록 프로그래밍 되었다.
이처럼 데이터와 기능을 별개로 보지 않고 하나로 묶어서 프로그래밍하는 방식을 객체 지향 프로그래밍이라고 한다.
객체 지향 프로그래밍에는 주요 개념 네 가지가 담겨 있다.
• 캡슐화: 데이터와 기능을 한데 묶어서 프로그래밍한다.
• 추상화: 복잡한 기능을 단순화해서 표현한다.
• 다형성: 같은 이름의 메서드라 하더라도 구체적인 작동 방식은 조금씩 다를 수 있다.
• 상속: 부모 클래스의 특징을 자식 클래스가 물려받는다.
이 중 상속에 대해서는 조금 더 부연할 필요가 있다.
앞에서 만든 청소부의 기능을 그대로 수행하면서 펫시팅도 할 수 있는 청소부를 만든다고 해보자.
class ClanerPlus{
constructor(name, department) {
this.name = name;
this.department = department;
}
clean() {
return `${this.name} has cleaned the ${this.department}.`;
}
petSitting(){
return `${this.name} can take care of pets.`;
}
}
위와 같이 기존의 Cleaner 클래스를 가져와서 기능을 더해주는 식으로 할 수도 있을 것이다.
그렇지만 CleanerPlus는 Cleaner와 중복된 속성과 메서드가 많다.
Cleaner의 속성과 메서드를 그대로 가져오면서도 기능을 추가하려면 extends 키워드를 사용해 상속을 구현할 수 있다.
class CleanerPlus extends Cleaner {
petSitting() {
return `${this.name} can take care of pets.`;
}
}
let cleaner3 = new CleanerPlus("Yoon", "bathroom");
console.log(cleaner3.clean()); //Yoon has cleaned the bathroom.
console.log(cleaner3.petSitting()); //Yoon can take care of pets.
여기서 한 걸음 더 나아가 CleanerPlus만 가지고 있는 속성을 추가해 보자.
기존 Cleaner 클래스보다 많은 전달인자를 추가로 받으려면 constructor를 다시 수행해야 한다.
또, super 키워드를 사용해 Cleaner 클래스가 가진 기능을 받아올 수 있다.
class CleanerPlus extends Cleaner {
constructor(name, department, pet) {
super(name, department);
this.pet = pet;
}
petSitting() {
return `${this.name} can take care of ${this.pet}.`;
}
}
let cleaner3 = new CleanerPlus("Yoon", "bathroom", "cats");
console.log(cleaner3.clean()); //Yoon has cleaned the bathroom.
console.log(cleaner3.petSitting()); //Yoon can take care of cats.
이렇게 하면 중복을 피하면서 기존 클래스의 기능을 상속받아 새로운 클래스를 만들 수 있다.
객체 지향 프로그래밍을 잘 활용하면 코드의 재사용성을 높이고 기능의 응축(추상화)을 쉽게 달성할 수 있다.
'Javascript' 카테고리의 다른 글
[Javascript] 비동기 프로그래밍 (0) | 2023.03.20 |
---|---|
[Javascript] 프로토타입 체인 (0) | 2023.03.16 |
[Javascript] 고차함수 (0) | 2023.03.14 |
[Javascript] DOM (0) | 2023.03.07 |
[Javascript] 구조 분해 할당 (0) | 2023.03.06 |