Post

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) 의존 역전 원칙

  • “추상회에 의존해야지, 구체화에 의존하면 안된다.”
  • 변하기 어려운 추상적인 것에 의존해야 한다는 것입니다.
    • 즉, 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻입니다.
  • 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안됩니다.
    • 이상적인 형태는 저수준 모듈이 변경되어도 고수준 모듈은 변경이 필요없는 것입니다.
This post is licensed under CC BY 4.0 by the author.