본문 바로가기

자바스크립트/기초

객체 지향 프로그래밍(OOP, Object-oriented programming)

객체 지향 프로그래밍이란?

객체 지향 프로그래밍은 하나의 모델이 되는 청사진(blueprint)을 만들고, 그 청사진을 바탕으로 한 객체를 만드는 프로그래밍 패턴입니다.

 

앞서 언급된 청사진은 자동차 생산을 위한 설계도에 비유됩니다. 자동차가 기능하기 위해서는 네 바퀴와 핸들, 좌석, 그리고 엔진이 필요할 것입니다. 이러한 기본적인 설계는 차의 종류에 상관없이 대체적으로 동일하게 적용됩니다.

이런 설계도(청사진)을 바탕으로 각각의 객체가 특정한 자동차 모델로 나오게 되는 것입니다.

 

사실, 이미 자바스크립트에는 "객체"라는 개념이 객체 지향 프로그래밍과 무관하게 이미 존재합니다. 따라서 자바스크립트에서는 용어를 잘 구분하는 것이 중요합니다.

앞으로, 그냥 객체가 아닌 "청사진"을 바탕으로 한 객체는 인스턴스 객체(instance object), 줄여서 인스턴스(instance)라고 부르겠습니다. 청사진은 클래스(class)라고 부릅시다.

 

객체를 어떤식으로 만드는지 살펴보면, 그냥 일반적인 함수를 정의하듯 만듭니다. 이때, 함수를 이용하는 방법이 조금 다릅니다. 그냥 실행하는 것이 아니고 new 키워드를 써서 만듭니다. 이는 새로운 인스턴스를 만드는 것입니다.

출처 : 코드스테이츠

클래스를 그냥 함수로 정의하면, 일반적인 다른 함수와 어떻게 구분하냐고요? 클래스를 만드는 암묵적인 규칙으로, 보통 클래스는 대문자 그리고 일반명사로 만듭니다. 따라서 일반적인 함수를 만들 때에는, 적절한 동사를 포함하고, 소문자로 시작하도록 만들면 좋습니다.

 

그리고 클래스를 만드는 새로운 문법이 ES6에 도입되었습니다. 바로 class 키워드입니다.

최근에는 ES6 방법을 주로 사용합니다.

// ES5 클래스는 함수로 정의할 수 있습니다.
function Car(brand, name, color) {
 // 인스턴스가 만들어질 때 실행되는 코드
}

// ES6 에서는 class라는 키워드를 이용해서 정의할 수도 있습니다
class Car {
  constructor(brand, name, color) {
  // 인스턴스가 만들어질 때 실행되는 코드
  }
}

여기서 보이는 함수는, 객체지향 프로그래밍에서 생성자(constructor) 함수라고 부릅니다. 인스턴스가 만들어질 때 실행되는 코드입니다.참고로, 생성자 함수는 return 값을 만들지 않습니다.

 

// new 키워드를 통해 클래스의 인스턴스를 만들어낼 수 있습니다.

let avante = new Car('hyundai', 'avante', 'black');
let mini = new Car('bmw', 'mini', 'white');
let beetles = new Car('volkswagen', 'beetles', 'red');
// 각각의 인스턴스는 Car라는 클래스의 고유한 속성과 메소드를 갖습니다

인스턴스를 만들 때에는 new 키워드를 사용합니다. 즉시 생성자 함수가 실행되며, 변수에 클래스의 설계를 꼭 닮은 새로운 객체, 즉 인스턴스가 할당됩니다. 각각의 인스턴스는 클래스의 고유한 속성과 메소드를 갖게 됩니다.

속성과 메소드

속성과 메소드라는 용어는 현실 세계를 생각해보면 조금 더 쉽게 와닿습니다.

예를 들어 자동차의 속성은, 브랜드, 차 이름, 색상, 현재 연료 상태, 최고 속력 등이 있을 수 있습니다.

메소드는 쉽게 말해 "객체에 딸린 함수"입니다. 연료 주입, 속력 설정, 운전 등이 메소드입니다.

this 란? - 속성의 정의

function Car(brand, name, color) {
  this.brand = brand;
  this.name = name;
  this.color = color;
  }
  
class Car {
   constructor(brand, name, color) {
       this.brand = brand;
       this.name = name;
       this.color = color;
   }
}

this는 한마디로 인스턴스 객체를 의미합니다. parameter로 넘어온 브랜드, 이름, 색상 등은 인스턴스 생성시 지정하는 값이며,

위와 같이 this에 할당한다는 것은 만들어진 인스턴스에 해당 브랜드, 이름, 색상을 부여하겠다는 의미입니다.

메소드의 정의

ES5는 prototype이라는 키워드를 사용해야 메소드를 정의할 수 있습니다. Car 클래스에 메소드를 추가하기 위해서는 'Car.prototype.refuel'과 같이 'prototype'을 이용해야 합니다.

 

ES6에서는 생성자 함수와 함께 class 키워드 안쪽에 묶어서 정의합니다. `refuel() {}`, `drive() {}`와 같이 작성합니다.

 

prototype 모델의 청사진을 만들 때 쓰는 원형 객체(original form)입니다.
constructor 인스턴스가 초기화될 때 실행하는 생성자 함수
this 함수가 실행될 때, 해당 scope마다 생성되는 고유한 실행 context(execution context)
new 키워드로 인스턴스를 생성했을 때에는, 해당 인스턴스가 바로 this의 값이 됨

절차적 언어란?

초기의 프로그래밍 언어는 일반적으로 절차적 언어라고 부릅니다(C, 포트란 등)

절차적 언어는 순차적인 명령의 조합입니다.

객체 지향 언어란?

"클래스"라고 부르는 데이터 모델의 청사진을 사용해 코드를 작성하는 언어입니다.

현대의 언어들(Java, C++, C# 등)은 대부분 객체 지향의 특징을 갖고 있습니다.

자바스크립트는 엄밀히 말해 객체 지향 언어는 아니지만, 객체 지향 패턴으로 작성할 수 있습니다.


OOP는 프로그램 설계 철학 중 하나입니다. OOP는 객체로 그룹화됩니다. 이 객체는 한번 만들고 나면, 메모리상에서 반환되기 전까지 객체 내의 모든 것이 유지됩니다. OOP의 4가지 주요 개념을 통해 재사용성을 얻을 수 있습니다.

클래스와 인스턴스

클래스는, 세부 사항(속성)이 들어가지 않은 청사진입니다. 세부 사항만 넣는다면, 객체가 되는 것입니다.

JavaScript에서 사용하는 용어와 별개로 클래스를 통해 만들어진 객체를 특별히 인스턴스 객체, 줄여서 인스턴스라고 부릅니다.

 

그렇다면 세부 사항은 언제 어떻게 넣어줘야 할까요? 이 역할을 하는 것이 바로 생성자입니다. 생성자를 통해 세부 사항(속성)을 넣어줍니다. 함수에 인자를 넣듯, 속성을 넣을 수 있습니다.

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

  • 캡슐화 (Encapsulation)
    • 데이터와 기능을 하나의 단위로 묶는 것
    • 은닉(hiding) : 구현은 숨기고, 동작은 노출시킴
    • 느슨한 결합(Loose Coupling)에 유리 : 언제든 구현을 수정할 수 있음
  • 상속 (Inheritance)
  • 추상화 (Abstraction)
  • 다형성 (Polymorphism)

캡슐화

캡슐화는 데이터(속성)와 기능(메소드)을 따로 정의하는 것이 아닌, 하나의 객체 안에 넣어서 묶는 것입니다. 데이터(속성)과 기능(메소드)들이 느슨하게 결합되는 것이죠.

느슨한 결합은 코드 실행 순서에 따라 절차적으로 코드를 작성하는 것이 아니라, 코드가 상징하는 실제 모습과 닮게 코드를 모아 결합하는 것을 의미합니다. 마우스 구동을 위한 코드 작성을 예로 들겠습니다. 스위치가 눌리고, 전기 신호가 생겨서, 전선을 타고 흐르고..와 같은 전 과정을 이곳 저곳에 나누어 작성하는 것이 아니라, 마우스의 상태를 속성(property)로 정하고 클릭, 이동을 메소드로 정해서 코드만 보고도 인스턴스 객체의 기능을 상상할 수 있게 작성하는 것이 느슨한 결합을 추구하는 코드 작성법입니다.

캡슐화라는 개념에는 "은닉화"의 특징도 포함하고 있는데, 은닉화는 내부 데이터나 내부 구현이 외부로 노출되지 않도록 만드는 것입니다. 따라서 디테일한 구현이나 데이터는 숨기고, 객체 외부에서 필요한 동작(메소드)만 노출시켜야 합니다. 은닉화의 특징을 살려서 코드를 작성하면 객체 내 메소드의 구현만 수정하고, 노출된 메소드를 사용하는 코드 흐름은 바뀌지 않도록 만들 수 있습니다. 반면 절차적 코드의 경우 데이터의 형태가 바뀔 때에 코드의 흐름에 큰 영향을 미치게 되어 유지보수가 어렵습니다. 그래서 더 엄격한 클래스는 속성의 직접적인 접근을 막고, 설정하는 함수(setter), 불러오는 함수(getter)를 철저하게 나누기도 합니다.

상속

상속은 부모 클래스의 특징을 자식 클래스가 물려받는 것입니다.

부모/자식으로 이야기하기도 하지만, 보다 그 특징을 자세하게 설명하는 용어는 "기본 클래스(base class)의 특징을 파생 클래스(derive class)가 상속받는다"로 표현하는 것이 적합합니다.

예를 들어, 사람이라는 클래스가 있다고 가정해봅시다. 사람은 기본적으로 이름과 성별, 나이와 같은 속성과 먹다, 자다 등과 같은 메소드가 있다고 볼 수 있습니다.

추가적으로 학생이라는 클래스를 작성한다고 생각해봅시다. 그런데 이 때 사람 클래스의 속성과 메소드를 재구현한다면 비효율적일 것입니다. 학생의 본질은 결국 사람이므로, 상속을 이용하여 학생 클래스는 사람 클래스를 상속받을 수 있습니다. 학생은 추가적으로 학습 내용, 공부하다와 같은 속성/메소드를 추가할 뿐인것입니다.

추상화

추상화는 내부 구현은 아주 복잡한데, 실제로 노출되는 부분은 단순하게 만든다는 개념입니다.

이러한 추상화를 통해 인터페이스가 단순해집니다. 너무 많은 기능들이 노출되지 않은 덕분에 예기치 못한 사용상의 변화가 일어나지 않도록 만들 수 있습니다.

클래스 정의 시, 메소드와 속성만 정의한 것을 인터페이스라고 부릅니다. 이것이 추상화의 본질입니다.

 

추상화는 캡슐화와 비교해서 종종 헷갈려하는 개념 중 하나입니다.

캡슐화코드나 데이터의 은닉에 포커스가 맞춰져있다면, 추상화는 클래스를 사용하는 사람이 필요하지 않은 메소드 등을 노출시키지 않고, 단순한 이름으로 정의하는 것에 포커스가 맞춰져 있습니다.

다형성

Poly = "많은" + morph = "형태"

즉, 다양한 형태를 가질 수 있다는 의미인 다형성은 하나의 변수명, 함수명 등이 상황에 따라 다른 의미로 해석될 수 있는 것입니다.

OOP의 주요 개념에 대한 장점

  • 캡슐화 (Encapsulation)
    • 코드가 복잡하지 않게 만들고, 재사용성을 높입니다
  • 상속 (Inheritance)
    • 불필요한 코드를 줄여 재사용성을 높입니다
  • 추상화 (Abstraction)
    • 코드가 복잡하지 않게 만들고, 단순화된 사용으로 인해 변화에 대한 영향을 최소화 합니다
  • 다형성 (Polymorphism)
    • 동일한 메소드에 대해 if/else if와 같은 조건문 대신 객체의 특성에 맞게 달리 작성하는 것이 가능해집니다

OOP의 의미

사람이 세계를 보고 이해하는 방법을 흉내낸 방법론

Must know concepts

  • 추상화와 캡슐화는 어떻게 다른가요? 
    • 캡슐화가 코드나 데이터의 은닉에 포커스가 맞춰져있다면, 추상화는 클래스를 사용하는 사람이 필요하지 않은 메소드 등을 노출시키지 않고, 단순한 이름으로 정의하는 것에 포커스가 맞춰져 있습니다.
  • 인터페이스(Interface)란 무엇일까요?
    • 클래스 정의 시, 메소드와 속성만 정의한 것을 인터페이스라고 부릅니다. 이것이 추상화의 본질입니다.
  • JavaScript에서 class 키워드를 사용하면, 메소드의 은닉이 가능한가요?
    • Yes

클래스와 프로토타입

프로토타입은 원형 객체를 의미합니다. 

자바스크립트는 흔히 프로토타입 기반 언어(prototype-based language)라 불립니다. 

출처 : 코드스테이츠

class Human {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  sleep() {
    console.log(`${this.name}은 잠에 들었습니다`);
  }
}

let steve = new Human('Steve', 30);

// 실습해보세요
Human.prototype.constructor === Human; // true
Human.prototype === steve.__proto__; // true
Human.prototype.sleep === steve.sleep; // true

프로토타입 체인

객체 지향 프로그래밍의 특성 중 "상속"을 구현할 때에는 프로토타입 체인을 사용합니다. 

  • addEventListener 속성은 어떤 클래스의 프로토타입에서 찾을 수 있나요? eventTarget
  • remove 메소드는 어떤 클래스의 프로토타입에서 찾을 수 있나요? Element
  • 모든 객체에 toString() 메소드가 존재하는 이유가 무엇인가요? 모든 객체에는 객체가 텍스트 값으로 표시되거나 객체가 문자열이 예상되는 방식으로 참조 될 때 자동으로 호출되는 toString() 메서드가 있습니다. 기본적으로 toString() 메서드는 Object에서 비롯된 모든 객체에 상속됩니다. 이 메서드가 사용자 지정 개체에서 재정의되지 않으면 toString()은 "[object type]"을 반환합니다. 여기서 type은 object type입니다. 
반응형