[JS] 궁금해서 정리해 본 '자바스크립트의 객체지향 프로그래밍(Object-Oriented-Programming)'

Kongveloper·2023년 9월 7일

객체지향 프로그래밍 (Object-Oriented Programming)

  • 실제 세계에서 사물을 인지하는 방식(객체, Object)을 프로그래밍에 접목하기 위해 객체의 핵심적인 개념, 기능만을 추출하는 추상화(abstraction)를 통해 모델링하려는 프로그래밍 패러다임.
    - 객체라는 사물을 생산하는 방식의 프로그래밍

클래스 기반 언어 VS. 프로토타입 기반 언어

클래스 기반 언어

클래스로 객체의 자료 구조와 기능을 정의하고, 생성자를 통해 인스턴스를 생성

  • 모든 인스턴스는 클래스에서 정의된 범위 내에서만 작동하며, 런타임에 구조를 변경할 수 없기 때문에, 정확성, 안정성, 예측성이 프로토타입 기반 언어보다 뛰어나다.

클래스

객체 생성에 사용되는 패턴, blueprint로, new 연산자를 통한 인스턴스화 과정이 필수적임.

  • 같은 종류의 집단에 속하는 속성과 행위를 정의한 것.
  • 객체지향 프로그램의 기본적인 사용자 정의 데이터형.

자바스크립트 : 프로토타입 기반 언어

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

  • 이미 생성된 인스턴스의 자료 구조와 기능을 동적으로 변경할 수 있음.
  • 프로토타입의 체인, 클로저 개념을 통해 객체 지향의 상속, 캡슐화(정보 은닉) 구현 가능.
  • 자바스크립트에는 다른 클래스 기반의 언어에서 사용하는 동일한 개념의 클래스 개념은 없지만, "객체 리터럴, Object() 생성자 함수, 생성자 함수" 객체 생성 방법을 지원한다.
    - ES6에서 등장한 class도 내부는 prototype으로 구성되어 있다.

    1. literal
    
    2. new 생성자 함수
    
    3. Object.create();
// 객체 리터럴
var obj1 = {};

// Object() 생성자 함수
var obj2 = new Object();

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

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

1. {} Object literal

변수에 직접 객체를 작성해 넣는 방법.

  • 장점 : 간단하게 작성 가능.
  • 단점 : 외부에서 내부 속성에 접근할 수 있기에 객체의 기능을 임의로 조작 가능.
const soundBox = {
  volume: 0,
  volumeUp: function() {
    if (this.volume < 10) {
      this.volume++;
    }
  },
  volumeDown: function () {
    if (this.volume > 0) {
      this.volume--;
    }
  }
};

// 객체 리터럴 방식의 단점 
soundBox.volume = 500;
  • soundBox의 모든 속성에 접근할 수 있기 때문에 볼륨이 500으로 변경되는 문제가 발생한다.
  • 사용자가 메소드로만 볼륨 조절을 할 수 있게 제한하여 의도적인 조작 혹은 실수로 인한 볼륨 임의 조작을 막을 필요가 있다 ➡️ ✅ 캡슐화(Encapsulation)

2. new 생성자 함수

new 연산자는 사용자 정의 객체 타입 또는 내장 객체 타입의 인스턴스를 생성한다.

  • 인스턴스 : 생성자 함수를 정의하고 new 연산자와 함께 생성자 함수를 호출해서 생성한 객체
    - 생성자 함수의 자식 개념

3. Object.create()

Object.create(obj.prototype)은 obj.protytpe을 protytpe으로 하는 객체를 생성하는 것.

  • 객체 생성으로 인해 끊어진 constructor를 재연결해줘야 상위 메소드와 본인이 가지고 있는 메소드를 모두 사용할 수 있음.

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

생성자 함수(Constructor - 클래스이자, 생성자의 역할)와 new 연산자를 통해 인스턴스를 생성하는 자바스크립트.

//생성자 함수(Constructor)
function Person(age){
	this.age = age; // 프로퍼티
    
    this.setAge = function(age){ // 메소드
    	this.age=age;
    }
    
    this.getAge = function(){ // 메소드
    	return this.age;
    }

}


//인스턴스 생성
let rm = new Person(20){
	console.log(rm.getAge()); //20
}

//메서드 호출
rm.setAge(17);
console.log(rm.getAge()); //17

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

프로토타입 : 다른 객체를 가리키는 내부 링크
프로토타입 체인 : 프로토타입을 통해 직접 객체를 연결하는 것.

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

// 프로토타입 객체에 메소드 정의
Person.prototype.setAge = function(age){
	this.age=age;
}

Person.prototype.getAge = function(){
	return this.age;
}

let rm = new Person(20);
let amy = new Person(27);

console.log(rm); // Person {age: 20}
console.log(rm); // Person {age: 27}

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

상속(Inheritance)

  • 코드 재사용 : 새롭게 정의할 클래스가 기존 클래스와 유사하다면, 상속을 통해 다른 점만 구현한다.

자바스크립트 : 프로토타입을 통해 객체가 다른 객체로 직접 상속된다.

생성자 함수 통한 대량 생산

  • 생성자 함수에 재사용할 메소드를 prototype으로 만든 후, new 키워드 통해 instance를 생성
  • 생성된 instance는 prototype chain을 통해 생성자 함수의 메소드를 중복된 코드 없이 사용할 수 있게 됨.
  • 대량생산은 가능하지만, 객체 리터럴 방식과 마찬가지로 외부에서 내부 값을 변경할 수 있음.
function soundBox() {
  this.volume = 0;
  
  Headphone.prototype.volumeUp = function () {
    if (this.volume < 10) {
      this.volume++;
    }
  }
  
  Headphone.prototype.volumeDown = function () {
    if (this.volume > 0) {
      this.volume--;
    }
  }
}

const box1 = new soundBox();
const box2 = new soundBox();

// 외부에서 내부 값을 변경할 수 있음
soundBox.volume = 500;

Factory 함수 통한 '캡슐화 + 대량 생산'

  • factorySoundBox 함수가 반환하는 객체의 속성인, 메서드를 사용해서 상위 스코프에 숨겨진 volume 변수를 내부에서만 조절할 수 있게 하는 방식.
function factorySoundBox () {
  let volumne = 0;
  
  return {
    volumeUp: function () {
      if (volume < 10) {
        volume++;
      }
    },
    volumeDown: function () {
      if (volume > 0) {
        volume--;
      }
    }
  };
}


const box1 = factorySoundBox();
const box2 = factorySoundBox();

의사 클래스 패턴 상속 (Pseudo-classical Inheritance)

자식 생성자 함수의 프로토타입 프로퍼티를 부모 생성자 함수의 인스턴스로 교체하여 상속 구현.

  • 클래스 기반 언어의 상속 방식을 흉내내는 것.

[문제점]

  • new 연산자를 통해 인스턴스를 생성하는 것은 자바스크립트의 프로토타입 본질에 모순된다.
  • 생성자 링크의 파괴 : 프로토타입 객체를 인스턴스로 교체하는 과정에서 constructor의 연결이 깨진다.
  • 생성자 함수를 사용하는 의사 클래스 패턴 상속은 객체 리터럴 패턴으로 생성한 객체의 생성에는 적합하지 않다.
//부모 생성자 함수
var Parent = ( function(){

//생성자 함수
function Parent(age){
	this.age = age;
}

//메소드
Parent.prototype.tellAge = function(){
	console.log('I'm ' + this.age +' years old';
};

// 생성자 함수 반환
return Parent;

}()
);

//자식 생성자 함수
var Child = ( function(){

	// 생성자 함수
    function Child(age){
	this.age = age;
}

// 자식 생성자 함수의 프로토타입 객체를 부모 생성자 함수의 인스턴스로 교체.
	Child.prototype = new Parent();
    
    // 메소드 오버라이드
    Child.prototype.tellAge = function(){
    	console.log('내 나이는 ' + this.age);
    }
    
    //askAge 메서드는 Parent 생성자 함수 인스턴스에 위치하게 됨.
    Child.prototype.askAge = function(){
    	console.log('네 나이는 뭐야? 내 나이는 ' + this.age);
    }
    
    return Child;
}()
);

let child = new Child('7');
console.log(child); // Parent { age: 7 }
console.log(Child.prototype); // Parent { age: undefined, tellAge: [Function], askAge: [Function] }

child.tellAge(); //내 나이는 7
child.askAge(); //네 나이는 뭐야? 내 나이는 7

프로토타입 패턴 상속 (Prototypal Inheritance)

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

  • Object.create 함수 : 매개변수에 프로토타입으로 설정할 객체 또는 인스턴스를 전달하고, 이를 상속하는 새로운 객체를 생성함.
    - 크로스 브라우징 주의 필요
var Parent = ( function(){
	//생성자 함수
    function Parent(age){
    this.age = age;
    }
    
    //method
    Parent.prototype.tellAge = function(){
    	console.log('I'm '+ this.age);
    };
    
    return Parent;
}()

);

create 함수의 인수는 프로토타입
var child = Object.create(Parent.prototype)
child.age = 7;

child.tellAge(); //I'm 7

캡슐화(Encapsulation)와 모듈 패턴

캡슐화 : 관련있는 멤버 변수와 메소드를 클래스와 같은 하나의 틀 안에 담고 외부에 공개될 필요가 없는 정보는 숨기는 것, 정보 은닉
특정 속성을 외부에 노출시키지 않고 내부에서만 조작하는 방법

즉시 실행 함수로 객체를 반환하여 scope 사용하기

즉시 실행 함수에 노출시키지 않을 데이터를 변수에 담아 scope로 묶어놓고, 반환되는 객체 메소드에서만 접근할 수 있도록 하는 방법

const soundBox = (function () {
  let volume = 0;
  
  return {
    volumeUp: function() {
      if (volume < 10) {
        volume++;
      }
    },
    volumeDown: function () {
      if (volume > 0) {
        volume--;
      }
    }
  };
})();
  • closer 개념을 사용해 soundBox 메서드를 통해 함수의 스코프 내에 접근하여 volume 변수에 접근에 조작할 수 있다.
    - 반환하는 객체에 soundBox.volume은 포함시키지 않았기 때문에, 해당 변수 자체는 외부에 노출되지 않아, 객체 리터럴 방식처럼 임의 조작을 불가능하게 막을 수 있다.

다형성

한 객체가 여러 가지 '형태'를 가질 수 있음을 의미하며, 같은 이름의 메소드나 함수를 호출했을 때, 그 동작이 실행되는 객체의 타입에 따라 다르게 작동하게 하는 것.

메소드 오버라이딩(Method Overriding)

같은 이름의 메소드나 함수가 객체의 타입에 따라 다르게 동작할 수 있도록 하는 '다형성'은 코드의 재사용성과 유연성, 가독성을 향상시킨다.

  • Dog과 Cat 클래스는 Animal 클래스를 상속받고 makeSound 메소드를 '오버라이딩'하여 makeSound 메소드는 다형적으로 동작한다.
class Animal {
  makeSound() {
    console.log('Some generic animal sound');
  }
}

class Dog extends Animal {
  makeSound() {
    console.log('Woof! Woof!');
  }
}

class Cat extends Animal {
  makeSound() {
    console.log('Meow! Meow!');
  }
}

let myAnimal = new Animal();
myAnimal.makeSound(); // Some generic animal sound

let myDog = new Dog();
myDog.makeSound(); //  Woof! Woof!

let myCat = new Cat();
myCat.makeSound(); //  Meow! Meow!

🙋‍♀️ 반드시 짚고 넘어가야 하는 핵심적인 질문들

1. 객체지향 프로그래밍(Object-Oriented Programming, OOP)이란?
객체 지향 프로그래밍(Object-Oriented Programming, OOP)은 프로그래밍 패러다임 중 하나로, 데이터와 기능을 하나로 묶어 객체(object)라는 단위로 표현(객체는 데이터와 그 데이터를 조작하는 함수를 캡슐화함)하는 방법론입니다. OOP는 실세계의 객체와 그 객체들 간의 상호작용에 초점을 둠으로써, 더 직관적이고 유연한 설계와 코드 재사용성을 증가시키는 것을 목표로 합니다. 객체지향의 주요 원칙에는 캡슐화, 상속, 다형성이 있습니다.

* 객체 지향 프로그래밍의 특징

2. 캡슐화(Encapsulation)란 무엇인가?
캡슐화는 객체의 내부 상태를 외부로부터 숨기고, 오직 정의된 인터페이스를 통해서만 접근을 허용하여 체의 내부 데이터를 보호하는 것.
이는 객체의 내부 구현을 수정하더라도 해당 객체를 사용하는 코드에는 영향을 미치지 않게 하는 장점이 있습니다.

3. 상속(Inheritance)이란?
상속은 기존 클래스의 속성과 메소드를 새로운 클래스가 물려받는 기능입니다. 이를 통해 코드의 재사용성이 높아지고, 유지 보수가 쉬워집니다.
(장점 : 코드 재사용성 증가 및 확장성)
그러나 잘못 사용하면 오버라이딩 오류나 불필요한 코드 복잡성을 초래할 수 있습니다.

4. 다형성(Polymorphism)이란?
다형성은 하나의 인터페이스나 클래스가 다양한 형태로 동작할 수 있게 하는 기능입니다. 예를 들어, 같은 메소드 이름을 가지지만 파라미터나 반환값이 다른 여러 함수를 정의할 수 있습니다.(같은 이름의 메서드가 다른 기능을 하는 것을 허용한다.) 이를 통해 코드의 유연성과 가독성이 향상됩니다.

5. 추상화(Abstraction)란?
복잡한 시스템을 간단한 개념으로 표현하는 것. 핵심적인 부분만을 표현함으로써 복잡성을 줄이고 명확하게 만든다.

6. 추상 클래스와 인터페이스의 차이는?
추상 클래스는 구현되지 않은 메소드를 포함할 수 있으며 상속을 통해 사용됩니다. 한 클래스는 하나의 추상 클래스만 상속할 수 있습니다. 인터페이스는 구현체가 없는 메소드만을 가지며, 다중 상속이 가능합니다. 추상 클래스는 상태(멤버 변수)를 가질 수 있지만, 인터페이스는 상태를 가질 수 없습니다.

  • 자바스크립트에는 추상 클래스와 인터페이스라는 개념이 명시적으로 존재하지 않지만, 이러한 개념을 다양한 방법으로 구현하거나 흉내 낼 순 있다.

7. Composition과 Aggregation의 차이는?
Composition은 한 객체가 다른 객체를 포함하며, 내부 객체의 생명주기가 외부 객체와 연결되어 있는 경우를 말합니다. Aggregation은 한 객체가 다른 객체를 포함하되, 내부 객체가 독립적인 생명주기를 가질 수 있는 경우를 말합니다.

8. 객체 지향 프로그래밍이 가지는 장단점은?
[장점]

  • 재사용성: 공통적인 로직과 속성을 가진 클래스를 다시 활용할 수 있어 코드 재사용성이 높아진다.
  • 확장성: 상속을 통해 기존 클래스를 확장하여 새로운 기능을 추가할 수 있다.
  • 유지 보수: 객체 단위로 코드가 구성되므로 버그 수정이나 기능 변경이 용이하다.
  • 직관적인 모델링: 실제 세계의 사물이나 관계를 객체로 쉽게 표현할 수 있다.

[단점]

  • 처리 속도: 객체 간의 메시지 전달, 상속 등의 오버헤드로 인해 처리 속도가 절차 지향보다 느릴 수 있다.
  • 복잡성: 객체와 클래스, 상속 등의 개념이 초보 개발자에게는 복잡하게 느껴질 수 있다.
  • 오버 엔지니어링: 실제 필요하지 않는 기능까지 고려하게 되어 개발 시간이 늘어날 수 있다.
  • 메모리 사용: 객체의 상태와 행위를 저장하는 데 추가적인 메모리가 필요할 수 있다.

참고 자료

객체 지향 프로그래밍
자바스크립트 객체 지향
자바스크립트에서 객체 지향을 하는 게 맞나?

profile
개발자

0개의 댓글