JS OOP 학습 정리
자바스크립트에서 OOP를 실현하기 위한 개념 중, 필자에게 익숙지 않거나 낯선 것을 학습하고 정리하였습니다고
프로토타입 Prototype
prototype: 원형(原型), 앞서 제작하는 시제품이자 표준, …
JS하면 빠질 수 없는 것이 프로토타입(Prototype)입니다. 프로토타입은 JS에서 객체가 속성을 상속받는 데 사용하는 메커니즘입니다. JS의 클래스 또한 새로운 상속 패턴을 만든 것이 아닌, 프로토타입을 기반으로 만들어졌습니다.
- 프로토타입은 디자인 패턴 중 하나입니다. 객체를 효율적으로 생성하는 방법으로, 비용이 큰 객체의 생성을 회피합니다.
- 프로토타입으로 객체를 생성하면 프로토타입의 메모리 공간을 공유하여 자원을 아낄 수 있습니다.
1
2
3
4
5
6
7
8
const myObject = {
city: "Madrid",
greet() {
console.log(`Greetings from ${this.city}`);
}
};
myObject.greet(); // Greetings from Madrid
위와 같은 객체 리터럴을 생성했다고 합시다. 이때, myObject.와 같이 객체의 속성에 접근하려 하면, city와 greet 말고도 다양한 속성들을 확인할 수 있을 겁니다. ex.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
__defineGetter__
__defineSetter__
__lookupGetter__
__lookupSetter__
__proto__
city
constructor
greet
hasOwnProperty
isPrototypeOf
propertyIsEnumerable
toLocaleString
toString
valueOf
- 이러한 속성은 자바스크립트의 모든 객체가 prototype이라는 내장된 속성을 가지고 있기 때문에 드러납니다. 프로토타입은 그자체로 객체이기에, 프로토타입도 자신의 프로토타입을 가지고, 이는 프로토타입 체인 prototype chan을 만듭니다. 이 체이닝은 프로토타입 속성에 null을 가지고 있는 프로토타입에 접근할 때 끝납니다.
자바스크립트는 모든 객체를 생성할 때 프로토타입을 사용하며, 이는 객체를 생성하기만 해도 프로토타입이 적용된다는 뜻입니다.
프로토타입 속성은 대부분의 브라우저에서
__proto__와 같은 이름으로 정의되어 있고,Object.getPrototypeOf()로 가져올 수 있습니다. 모든 객체는 기본적으로Object.prototype객체를 프로토타입으로 가집니다. (모든 객체의 프로토타입이Object.protytpe이라는 뜻은 아닙니다) 이 객체의 프로토타입 속성은 null이므로 프로토타입 체이닝의 끝이 되겠죠.속성 셰도잉 shadowing
1 2 3 4 5 6 7 8 9
const myDate = new Date(1995, 11, 17); console.log(myDate.getTime()); // 819129600000 myDate.getTime = function () { console.log("something else!"); }; myDate.getTime(); // 'something else!'
myDate는 프로토타입(Date)에 getTime 메소드가 있지만, shadowing으로 다시 정의됩니다. overriding을 알고 있다면 비슷하게 이해하면 됩니다. 다만, overriding의 경우는 같은 인자의 개수와 데이터타입이 일치할 때를 일컫는다고 합니다. 스택오버플로우 관련
프로토타입 생성
1 2 3 4 5 6
// 생성 방법 1. Object.create() const personPrototype = { greet() { console.log("hello!"); } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
const sj = Object.create(personPrototype); sj.greet(); // hello! // 생성 방법 2. 생성자 함수 이용 // js의 모든 함수는 prototype 속성을 가지며, 생성자로 불리면 이 속성이 새롭게 생성된 객체의 프로토타입이 됩니다. const personPrototype = { greet() { console.log(`hello, my name is ${this.name}!`); } }; function Person(name) { this.name = name; } Object.assign(Person.prototype, personPrototype); // 또는, Person.prototype.greet = personPrototype.greet const yj = new Person("Yoon"); yj.greet(); // hello, my name is Yoon!
- 추가로,
Object.hasOwn(객체, 메소드 혹은 변수)프로토타입으로 정의된 메소드는 hasOwn에서 false를 반환하여 구분됩니다.
상속
Inheritance 상속은 부모에서 자식으로 특성을 물려주며 새로운 코드가 기존 코드를 재사용할 수 있게 합니다. 상속은 프로토타입을 기반으로 구현됩니다. JS 객체는 프로토타입 객체와 연결되어 있기 때문에 객체의 속성에 접근하려 하면 그 속성은 해당 객체 뿐 아니라 객체의 프로토타입을 포함하여 찾게 됩니다.
- 객체의 프로토타입 속성은
Object.setPrototypeOf()Object.getPrototypeOf()으로 접근할 수 있습니다. obj.__proto__를 통해 직접 접근할 수도 있지만, 이는 표준이 아니며 자바스크립트 엔진에서 구현된 내용이므로 헷갈리게 할 수 있습니다.이때, 위의 접근자가 아니라 객체 리터럴을 생성할 때
{a: 1, b:2, __proto__: c}와 같은 문법은 표준입니다.생성자 함수를 이용한 일반적인 상속은 아래와 같이 구현됩니다.
1 2 3 4
function Constructor() {} const obj = new Constructor(); // obj ---> Constructor.prototype ---> Object.prototype ---> null
더 긴 프로토타입 체이닝을 구현하기 위해서는 아래와 같이 직접 프로토타입을 설정해줄 수 있습니다.
1 2 3 4 5 6 7 8 9
function Base() {} function Derived() {} Object.setPrototypeOf(Derived.prototype, Base.prototype); // Derived.prototype = Object.create(B으ase.prototype); // 주석 처리된 상속 체인 구현 방법은 prototype 속성을 재할당함과 동시에 constructor 속성을 삭제하기 때문에 문제가 될 수 있는 legacy code입니다. const obj = new Derived(); // obj ---> Derived.prototype ---> Base.prototype ---> Object.prototype ---> null
클래스 문법을 알고 있다면, 이는 다음과 같은 의미입니다.
1 2 3 4 5
class Base {} class Derived extends Base {} const obj = new Derived(); // obj ---> Derived.prototype ---> Base.prototype ---> Object.prototype ---> null
출처: MDN Web Docs
4 Pillars of OOP
- Abstraction
- Encapsulation
- Inheritance
- Polymorphism
SOLID
SRP (Single Responsibility Principle) 단일 책임 원칙
- 하나의 클래스는 하나의 책임만 가져야 한다는 뜻입니다.
클래스를 변경하는 이유는 단 하나여야 합니다.
- 변경이 있을 때 파급 효과가 적어야 합니다.
- 이를 지키지 않으면, 한 책임의 변경에 의해 다른 책임과 관련된 코드에 영향을 미칠 수 있습니다.
- 이때, 책임이란 기능이나 역할과 같이 생각하면 됩니다.
- 책임이 여러개면 클래스와 내부 함수끼리 결합을 가진 확률이 높아지고, 이는 좋지 않은 코드를 낳습니다.
OCP (Open-Closed Principle) 개방-폐쇄 원칙
- 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 합니다.
- 즉, 기존의 코드를 변경하지 않고 기능을 수정, 추가할 수 있도록 설계해야합니다.
인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현해야 합니다.
- 상속(다형성)과 추상화(인터페이스)를 활용합니다.
LSP (Liskov Substitution Principle) 리스코프 치환 원칙
- 하위 타입 객체는 상위 타입 객체에서 가능한 행위를 수행할 수 있어야 한다.
- 즉, 상위 타입 객체를 하위 타입 객체로 대체하여도 정상적으로 동작해야 한다.
ISP (Interface Segregation Principle) 인터페이스 분리 원칙
- 클라이언트는 자신이 사용하는 메소드에만 의존해야 한다.
DIP (Dependency Inversion Principle) 의존 역전 원칙
- “추상회에 의존해야지, 구체화에 의존하면 안된다.”
- 변하기 어려운 추상적인 것에 의존해야 한다는 것입니다.
- 즉, 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻입니다.
- 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안됩니다.
- 이상적인 형태는 저수준 모듈이 변경되어도 고수준 모듈은 변경이 필요없는 것입니다.