쑤쑤_CS 기록장

20. 자바스크립트 객체지향 프로그래밍 본문

IT 지식 기록/JavaScript 정리

20. 자바스크립트 객체지향 프로그래밍

(╹◡╹)_ 2020. 12. 24. 16:39
728x90

#1. 객체지향 프로그래밍 개요

객체지향 프로그래밍

: 실세계에 존재하고 인지하고 있는 객체(Object)를 소프트웨어의 세계에서 표현하기 위해 객체의 핵심적인 개념 또는 기능만을 추출하는 추상화(abstraction)를 통해 모델링하려는 프로그래밍 패러다임

 

관계성있는 객체들의 집합이라는 관점으로 접근하는 소프트웨어 디자인

#2. 클래스 기반 vs. 프로토타입 기반

#2.1 클래스 기반 언어

  • Java C++ C# Python PHP Ruby Object-C

  • 클래스란
    • 같은 종류의 집단에 속하는 속성(attribute)과 행위(behavior)를 정의한 것
    • 객체지향 프로그램의 기본적인 사용자 정의 데이터형(user define data type)
    • 객체 생성에 사용되는 패턴 혹은 청사진(blueprint)일 뿐
    • new 연산자를 통한 인스턴스화 과정이 필요하다.
  • 인스턴스란
    • 오직 클래스에서 정의된 범위 내에서만 작동하며
    • 런타임에 그 구조를 변경할 수 없다.
    • 정확성, 안정성, 예측성 측면에서 클래스 기반 언어가 프로토타입 기반 언어보다 좀 더 나은 결과를 보장한다.

#2.2 프로토타입 기반 언어

  • 멀티-패러다임 언어

  • 명령형(imperative), 함수형(functional), 프로토타입 기반(prototype-based) 객체지향 언어

객체 생성 방법 (3)

  • 객체 리터럴
  • Object() 생성자 함수
  • 생성자 함수
// 객체 리터럴
var obj1 = {};
obj1.name = 'Lee';

// Object() 생성자 함수
var obj2 = new Object();
obj2.name = 'Lee';

// 생성자 함수
function F() {}
var obj3 = new F();
obj3.name = 'Lee';

 

#3. 생성자 함수와 인스턴스의 생성

자바스크립트는 생성자 함수와 new 연산자를 통해 인스턴스를 생성할 수 있다. 이때 생성자 함수는 클래스이자 생성자의 역할을 한다.

// 생성자 함수(Constructor)
function Person(name) {
  // 프로퍼티
  this.name = name;

  // 메소드
  this.setName = function (name) {
    this.name = name;
  };

  // 메소드
  this.getName = function () {
    return this.name;
  };
}

// 인스턴스의 생성
var me = new Person('Lee');
console.log(me.getName()); // Lee

// 메소드 호출
me.setName('Kim');
console.log(me.getName()); // Kim

 

각 인스턴스가 내용이 동일한 메소드를 각자 소유한다. 이는 메모리 낭비인데 생성되는 인스턴스가 많아지거나 메소드가 크거나 많다면 무시할 수 없는 문제이다.

문제를 해결하려면 다른 접근 방식이 필요한데 그 해답은 프로토타입이다.

 

#4. 프로토타입 체인과 메소드의 정의

프로토타입

: 다른 객체를 가리키는 내부 링크

  • 프로토타입 체인 : 프로토타입을 통해 직접 객체를 연결

  • 프로토타입을 이용
    • 생성자 함수 내부의 메소드를 생성자 함수의 prototype 프로퍼티가 가리키는 프로토타입 객체로 이동
    • 생성자 함수에 의해 생성된 모든 인스턴스는 프로토타입 체인을 통해 프로토타입 객체의 메소드를 참조할 수 있다.
/**
 * 모든 생성자 함수의 프로토타입은 Function.prototype이다. 따라서 모든 생성자 함수는 Function.prototype.method()에 접근할 수 있다.
 * @method Function.prototype.method
 * @param ({string}) (name) - (메소드 이름)
 * @param ({function}) (func) - (추가할 메소드 본체)
 */
Function.prototype.method = function (name, func) {
  // 생성자함수의 프로토타입에 동일한 이름의 메소드가 없으면 생성자함수의 프로토타입에 메소드를 추가
  // this: 생성자함수
  if (!this.prototype[name]) {
    this.prototype[name] = func;
  }
};

/**
 * 생성자 함수
 */
function Person(name) {
  this.name = name;
}

/**
 * 생성자함수 Person의 프로토타입에 메소드 setName을 추가
 */
Person.method('setName', function (name) {
  this.name = name;
});

/**
 * 생성자함수 Person의 프로토타입에 메소드 getName을 추가
 */
Person.method('getName', function () {
  return this.name;
});

var me  = new Person('Lee');
var you = new Person('Kim');
var him = new Person('choi');

console.log(Person.prototype);
// Person { setName: [Function], getName: [Function] }

console.log(me);  // Person { name: 'Lee' }
console.log(you); // Person { name: 'Kim' }
console.log(him); // Person { name: 'choi' }

#5. 상속 (Inheritance)

프로토타입을 통해 객체가 다른 객체로 직접 상속된다.

  1. 의사 클래스 패턴 상속 : 클래스 기반 언어의 상속 방식과 유사하다.
  2. 프로토타입 패턴 상속 : 프로토타입으로 상속을 구현한다.

#5.1 의사 클래스 패턴 상속

  • 자식 생성자 함수의 prototype 프로퍼티를 부모 생성자 함수의 인스턴스로 교체하여 상속을 구현한다.
  • 부모와 자식 모두 생성자 함수를 정의하여야 한다
// 부모 생성자 함수
var Parent = (function () {
  // Constructor
  function Parent(name) {
    this.name = name;
  }

  // method
  Parent.prototype.sayHi = function () {
    console.log('Hi! ' + this.name);
  };

  // return constructor
  return Parent;
}());

// 자식 생성자 함수
var Child = (function () {
  // Constructor
  function Child(name) {
    this.name = name;
  }

  // 자식 생성자 함수의 프로토타입 객체를 부모 생성자 함수의 인스턴스로 교체.
  Child.prototype = new Parent(); // ②

  // 메소드 오버라이드
  Child.prototype.sayHi = function () {
    console.log('안녕하세요! ' + this.name);
  };

  // sayBye 메소드는 Parent 생성자함수의 인스턴스에 위치된다
  Child.prototype.sayBye = function () {
    console.log('안녕히가세요! ' + this.name);
  };

  // return constructor
  return Child;
}());

var child = new Child('child'); // ①
console.log(child);  // Parent { name: 'child' }

console.log(Child.prototype); // Parent { name: undefined, sayHi: [Function], sayBye: [Function] }

child.sayHi();  // 안녕하세요! child
child.sayBye(); // 안녕히가세요! child

console.log(child instanceof Parent); // true
console.log(child instanceof Child);  // true

Child 생성자 함수가 생성한 인스턴스 child(①)의 프로토타입 객체는 Parent 생성자 함수가 생성한 인스턴스(②)이다.

Parent 생성자 함수가 생성한 인스턴스의 프로토타입 객체는 Parent.prototype이다.

child는 프로토타입 체인에 의해 parent인스턴스와 Parent.prototype의 모든 프로퍼티에 접근 가능하다.

 

< 의사 클래스 패턴의 문제점 >

  1. new 연산자를 통해 인스턴스를 생성한다
  2. 생성자 링크의 파괴
  3. 객체 리터럴

#5.2 프로토타입 패턴 상속

Object.create 함수를 사용하여 객체에서 다른 객체로 직접 상속을 구현하는 방식

-> new 연산자가 필요없으며, 생성자 링크도 파괴되지 않으며, 객체리터럴에도 사용할 수 있다.

  • Object.create 함수 : 매개변수에 프로토타입으로 설정할 객체 또는 인스턴스를 전달하고 이를 상속하는 새로운 객체를 생성

    1. 비어있는 생성자 함수 F를 생성한다.
    2. 생성자 함수 F의 prototype 프로퍼티에 매개변수로 전달받은 객체를 할당한다.
    3. 생성자 함수 F를 생성자로 하여 새로운 객체를 생성하고 반환한다.

 

#6. 캡슐화와 모듈 패턴

캡슐화

: 관련있는 멤버 변수와 메소드를 클래스와 같은 하나의 틀 안에 담고 외부에 공개될 필요가 없는 정보를 숨기는 것

= 정보 은닉 (information hiding)

var Person = function(arg) {
  var name = arg ? arg : ''; // ①

  this.getName = function() {
    return name;
  };

  this.setName = function(arg) {
    name = arg;
  };
}

var me = new Person('Lee');

var name = me.getName();

console.log(name);

me.setName('Kim');
name = me.getName();

console.log(name);

 

①의 name 변수는 private 변수가 된다. 자바스크립트는 function-level scope를 제공하므로 함수 내의 변수는 외부에서 참조할 수 없다. 

public 메소드 getName, setName은 클로저로서 private 변수(자유 변수)에 접근할 수 있다. 이것이 기본적인 정보 은닉 방법이다.

 

 

person 함수는 객체를 반환한다. 이 객체 내의 메소드 getName, setName은 클로저로서 private 변수 name에 접근할 수 있다. 이러한 방식을 모듈 패턴이라 하며 캡슐화와 정보 은닉를 제공한다. 많은 라이브러리에서 사용되는 유용한 패턴이다.

 

< 주의할 점 >

  • private 멤버가 객체나 배열일 경우, 반환된 해당 멤버의 변경이 가능하다
    : 얕은 복사가 일어나 private 멤버의 참조값을 반환하기 때문

  • person 함수가 반환한 객체는 person 함수 객체의 프로토타입에 접근할 수 없다 = 상속을 구현할 수 없다.

    → 모듈 패턴은 생성자 함수가 아니며 단순히 메소드를 담은 객체를 반환

    → 객체 리터럴 방식으로 생성된 객체로 person의 프로토타입에 접근 (X)

  • 이문제를 해결하기 위해서는 반환형을 객체가 아닌 함수로 해야 한다.

728x90
Comments