객체지향 프로그래밍

코이그·2023년 4월 18일

노드반 스터디

목록 보기
4/5

1. 객체지향 프로그래밍

  • 객체를 소프트웨어의 세계에서 표현하기 위해 객체의 핵심적인 개념 또는 기능만을 추출하는 추상화를 통해 모델링하려는 패러다임

  • 관계성있는 객체들의 집합

  • 별도의 역할이나 책임을 갖는 작은 독립적인 기계 또는 부품

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

클래스 기반 언어

  • Java, C++, Python 등
  • 클래스는 객체 생성에 사용되는 청사진이고 new 연산자를 통한 인스턴스화 과정 필요
  • 모든 인스턴스는 클래스에서 정의된 범위 내에서만 작동하고 런타임에 구조를 변경할 수 없음 -> 정확성, 안정성, 예측성 측면에서 장점

프로토타입 기반 언어

자바스크립트: 명령형, 함수형, 프로토타입 기반 객체지향 언어.

자바스크립트 객체 생성 방법:

  1. 객체 리터럴
    let obj = { "name": "lee" }
  2. Object() 생성자 함수
    let obj = new Object(); obj.name = "lee";
  3. 생성자 함수
    function F() {}
    let obj = new F();
    obj.name = "lee";
  • 클래스 기반 언어들과는 다르게 생성된 인스턴스의 구조와 기능을 동적으로 변경 가능.
  • 상속, 캡슐화 등의 개념은 프로토타입 체인과 클로저 등으로 구현 가능.

    ES6에서 클래스가 도입됨. 새로운 객체지향 모델이 아니라 클래스도 사실 함수이고 기존 프로토타입 기반 패턴의 syntactic sugar(더 나은 가독성과 표현력을 위한 문법)임.

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

  • 생성자 함수와 new 연산자로 인스턴스 생성 가능.
  • 생성자 함수 == 클래스 == 생성자
function Person(name) {
	this.name = name;
    
    this.setName = function(name) { this.name = name; };
    this.getName = function() { return this.name; };
}

const p1 = new Person("david");
console.log(p1.getName());

위와 같이 생성자 함수를 구현하고 new 연산자로 인스턴스를 생성할 수 있음.
하지만 위의 방법대로 여러 인스턴스를 생성하게 되면 인스턴스마다 메소드(getName, setName)이 중복되어 생성됨 --> 메모리 낭비

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

프로토타입 체인: 프로토타입을 통해 직접 객체를 연결하는 것. (모든 객체는 프로토타입(객체)이라는 내부 링크를 가지고 있음)

function Person(name) { this.name = name; }

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

  • 생성자 함수에는 프로토타입 객체를 가리키는 prototype 프로퍼티가 있음.
  • 프로토타입 체인에 의해 모든 인스턴스가 프로토타입 객체의 메소드를 참조할 수 있음.
  • 프로토타입 객체는 상속할 것들이 저장되는 장소.

프로토타입 객체에 메소드 추가하는 방식

// Function.prototype == 생성자 함수의 프로토타입
Function.prototype.method = function (name, func) {
	// 프로토타입의 name(메소드 이름)에 func(메소드 본체) 할당
	if (!this.prototype[name]) {
    	this.prototype[name] = func;
    }
};
function Person(name) { this.name = name; }

Person.method('setName', function (name) { this.name = name; });
Person.method('getName', function () { return this.name; });

5. 상속

상속 구현 방식 두 가지:

  1. 의사 클래스 패턴 상속(클래스 기반 언어의 상속 방식을 흉내내는 것)
  2. 프로토타입 패턴 상속(프로토타입으로 상속을 구현하는 것)

기본적으로 프로토타입을 통해 상속을 구현함(프로토타입을 통해 객체가 다른 객체로 직접 상속됨)

의사 클래스 패턴 상속

  • 자식 생성자 함수의 prototype 프로퍼티에 부모 생성자 함수의 인스턴스를 할당하여 상속을 구현하는 방법.
  • 부모와 자식 모두 생성자 함수를 정의해야 함.
let Parent = (function () {
	function Parent(name) { this.name = name; }
    Parent.prototype.sayHi = function () { console.log("hi " + this.name); };
    return Parent;
}());

let Child = (function () {
	function Child(name) { this.name = name; }
    Child.prototype = new Parent();
    Child.prototype.sayHi = function () { console.log("hi" + this.name); };
    Child.prototype.sayBye = function () { console.log("bye " + this.name); };
    return Child;
}());


Child에서 구현한 sayHi와 sayBye 모두 Parent 생성자 함수의 인스턴스 에 위치됨.(Child.prototype == new Parent()이기 때문에)

문제점

의사 클래스 패턴 상속은 구동 상 문제가 없지만 문제가 있음.

  1. new 연산자를 통한 인스턴스 생성.
    프로토타입 본질에 모순됨.
  2. 생성자 링크 파괴.
    객체는 내부 프로퍼티로 constructor를 가지는데 이는 생성자 함수를 가리킴. 위에서 Child의 프로토타입 객체에는 Parent 생성자 함수가 생성한 new Parent() 객체가 들어있음. 따라서 Child.constructor가 프로토타입 체인에 의해 Parent.prototyp의 constructor로 교체됨.
  3. 객체리터럴.
    의사 클래스 패턴 상속은 생성자 함수를 사용하기 때문에 객체리터럴 패턴으로 생성한 객체의 상속에는 적합하지 않음.(객체리터럴 패턴으로 생성한 객체의 생성자 함수인 Object()는 변경할 수 없음)

프로토타입 패턴 상속

Object.create() 메소드를 사용하여 객체에서 다른 객체로 직접 상속을 구현하는 방식. 의사 클래스 패턴 상속 구현에서 생기는 문제가 해결됨.

let Parent = (function () {
	function Parent(name) { this.name = name; }
    Parent.prototype.sayHi = function () { console.log("hi " + this.name); };
    return Parent;
}());

let child = Object.create(Parent.prototype);
child.name = 'child';
child.sayHi();


의사 클래스 패턴 상속 방식과 다르게 child가 Parent.prototype을 직접 가리킬 수 있음.

또한, 객체리터럴 패턴으로 생성한 객체에도 상속을 사용할 수 있음.

let parent = { 
	name: 'parent', 
    sayHi: function() { console.log("hi " + this.name); }
};
let child = Object.create(parent);
child.name = 'child';
parent.sayHi();
child.sayHi();

Object.create() 폴리필

if (!Object.create) {
  Object.create = function (o) {
    function F() {}  // 1
    F.prototype = o; // 2
    return new F();  // 3
  };
}
  1. 비어있는 생성자 함수 F 생성.
  2. 생성자 함수 F의 prototype 프로퍼티에 매개변수로 전달받은 객체 할당.
  3. 생성자 함수 F를 생성자로 하여 새로운 객체를 생성하고 반환.

6. 캡슐화와 모듈 패턴

캡슐화: 정보 은닉(관련있는 멤버 변수/메소드를 하나의 틀에 담고 외부에 공개될 필요가 없는 정보를 숨기는 것)
ex) 클래스 기반 언어의 public, private 등

모듈 패턴

let person = function(arg) {
	var name = arg;		// private
    this.name = arg;	// public
    
    // 클로저로서 private 변수 접근 가능
    return {
    	getName: function() { return name; },
        setName: function(arg) { name = arg; }
    }
}
let p1 = person("Lee");
let name = p1.getname();
p1.setName("Kim");
  1. person 함수는 객체를 반환함.
  2. 이 객체 내의 메소드 getName, setName은 클로저로서 private 변수에 접근할 수 있음.

주의할 점

  • private 멤버가 객체나 배열일 경우 반환된 해당 멤버(얕은 복사 변경 가능. (해결 방법: 깊은 복사로 복사본 반환)
  • 상속 구현 불가 (person 함수가 반환한 객체는 Person 함수 객체의 프로토타입에 접근 x)
    해결 방법: 객체가 아닌 함수 반환
let Person = function() {
	let name;
    let F = function(arg) { name = arg ? arg : ''; };
    
    F.prototype = {
    	getName: function() { return name; },
        setName: function(arg) { name = arg; }
    };
    return F;
}();

profile
COYG🔴⚪

0개의 댓글