13. [JavaScript] 객체 지향 프로그래밍__객체 지향

문도연·2022년 6월 9일
0

Chapter1. 객체 지향
Chapter1-1. 클로저 모듈 패턴
Chapter1-2. 클래스와 인스턴스
Chapter1-3. 객체 지향 프로그래밍
Chapter1-4. 객체 지향 차이점
Chapter2. 프로토타입
Chapter2-1. 프로토타입과 클래스
Chapter2-2. 프로토타입 체인


Chapter1. 객체 지향

  • 클로저 모듈 패턴에 대해 설명할 수 있다.
  • 클래스와 인스턴스에 대해 설명할 수 있다.
  • 클래스 문법을 이용할 수 있다.

객체 지향 프로그래밍(OOP, Object-oriented programming)은 사람이 세계를 보고 이해하는 방법과 매우 흡사하며,

절차 지향 프로그래밍과는 다르게 데이터와 기능을 한곳에 묶어서 처리합니다.

속성과 메서드가 하나의 "객체"라는 개념에 포함되며, 이는 자바스크립트 내장 타입인 object(이하, object literal)와는 다르게, 클래스(Class)라는 이름으로 부릅니다.

Chapter1-1. 클로저 모듈 패턴

메서드 호출 실습

메서드 호출은 객체.메서드() 과 같이 객체 내에 메서드를 호출하는 방법을 의미

⚠️ 메서드 호출 방식을 이용할 때에는 화살표 함수를 쓰지 않습니다.

//단순 객체를 사용한 counter 예시
let counter1 = {
	value: 0,
	increase: function() {
            this.value++ // 메서드 호출을 할 경우, this는 counter1을 가리킴
        },
	decrease: function() {
            this.value--
        },
	getValue: function() {
            return this.value
        }
}
    
counter1.increase()
counter1.increase()
counter1.increase()
counter1.decrease()
counter1.getValue() //2

클로저를 이용해 매번 새로운 객체 생성하기

위의 counter1은 단 하나의 객체만 만들 수 있습니다. 만약 똑같은 기능을 하는 카운터가 여러 개가 필요하다면, 이 코드를 여러 번 만들어야 할까요? 같은 코드를 그대로 복사/붙여넣기 해야 하므로, 재사용성이 떨어집니다.

똑같은 기능을 하는 카운터를 여러 개 만드는 방법 중 하나는, 아래 예제 코드와 같이 클로저 모듈 패턴을 이용할 수 있습니다.

//클로저 모듈 패턴으로 구현한 counter 예시
function makeCounter() {
    let value = 0;
    return {
        increase: function() {
            value++;
        },
        decrease: function() {
            value--;
        },
        getValue: function() {
            return value;
       }
    }
 }
 
 let counter1 = makeCounter()
 counter1.increase()
 counter1.getValue() // 1
 
 let counter2 = makeCounter()
 counter2.decrease()
 counter2.decrease()
 counter2.getValue() // -2
  • 특징1. 함수 makeCounter는 객체에 담긴 3개의 함수를 리턴한다.

  • 특징2. 3개의 내부함수는 외부함수의 변수인 value를 조작할 수 있다.

  • 특징3. 변수에 함수를 할당할 수 있다. 각 변수에 할당된 함수는 독립적으로 사용가능하며 서로의 값에 영향을 주지 않는다.


Chapter1-2. 클래스와 인스턴스

객체 지향 프로그래밍

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

청사진은 자동차 생산을 위한 설계도에 비유됩니다. 자동차가 기능하기 위해서는 네 바퀴와 핸들, 좌석 그리고 엔진이 필요할 것입니다. 이러한 기본적인 설계는 차의 종류에 상관없이 대체적으로 동일하게 적용됩니다. 이런 설계도(청사진)를 바탕으로 각각의 객체가 특정한 자동차 모델로 나오게 되는 것입니다.

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

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

청사진은 클래스(class)라고 부릅니다.

클래스와 인스턴스 만들기

클래스는 일반적인 함수를 정의하듯 만듭니다.

새로운 인스턴스를 만들 때는 new 키워드를 써서 만듦
일반적인 다른 함수와 구분하기 위해 클래스는 보통 대문자로 시작하며 일반명사로 만듭니다.

//class
function Car(color) {}

//instances
let avante = new Car('blue');
let tesla = new Car('red');
let genesis = new Car('white');

클래스 만드는 방법 (2)

//ES5
function Car(brand, name, color) {
 //인스턴스가 만들어질 때 실행되는 코드
 }

//ES6부터는 class 키워드를 사용해 만들고 최근에 이 방법을 주로 사용
class Car {
 constructor(brand, name, color) {
	//인스턴스가 만들어질 때 실행되는 코드
  }
 }

ES6에서 위 함수는 객체지향 프로그래밍에서 생성자(constructor) 함수라고 부릅니다.
생성자 함수는 return 값을 만들지 않습니다.

인스턴스 만드는 방법

let avante = new Car('hyundai', 'avante', 'blue');
let tesla = new Car('telsa', 'mini', 'red');
let genesis = new Car('hyundai','gv90', 'white');

인스턴스를 만들 때에 new 키워드를 사용합니다.
new 키워드가 실행되면
-> 생성자 함수가 실행 되며
-> 변수에 클래스의 설계를 가진 인스턴스가 할당됩니다.
-> 각각의 인스턴스는 Car라는 클래스의 고유한 속성과 메서드를 갖게 됩니다.

속성과 메서드

클래스에 속성과 메소드를 정의하고, 인스턴스에서 이용합니다.

속성메소드
brand
name
color
currentFuel
maxSpeed
refuel()
setSpeed()
drive()

자동차의 속성은 브랜드, 차 이름, 색상, 현재 연료 상태, 최고 속력 등이 있을 수 있습니다.
메서드는 "객체에 딸린 함수"입니다. 연료 주입, 속력 설정, 운전 등이 메서드입니다.

"학생"이라는 클래스를 만든다면

속성메소드
이름
나이
학년
내신
희망전공
일어나기()
공부하기()
밥먹기()
놀기()

"은행"이라는 클래스를 만든다면

속성메소드
계좌번호
소유주
잔액
이자율
입금하기()
출금하기()
이체하기()

이처럼 객체지향 프로그래밍은, 현실세계를 기반으로 프로그래밍 모델을 만들 때 유용하다.

클래스에서 속성 정의하기

// ES5
function Car(brand, name, color) {
	this.brand = brand;
    this.name = name;
    this.color =color;
}

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

this?

this는 인스턴스 객체를 의미합니다.
parameter로 넘어온 브랜드, 이름, 색상 등은 인스턴스 생성 시 지정하는 값이며,
위와 같이 this에 할당한다는 것은 만들어진 인스턴스에 해당 브랜드, 이름, 색상을 부여하겠다는 의미입니다.

클래스에서 메소드 정의하기

//ES5
function Car(brand, name, color) {/*생략*/}
	Car.prototype.refuel = function() {
	// 연료 공급을 구현하는 코드
	}

	Car.prototype.drive = fuction() {
	// 운전을 구현하는 코드
	}

//ES6
class Car {
 constructor(brand, name, color) {/*생략*/}
    refuel() {
    {
    
    drive() {
    {
}

ES5는 prototype이라는 키워드를 사용해야 메서드를 정의할 수 있습니다.

ES6에서는 생성자 함수와 함께 class키워드 안쪽에 묶어서 정의합니다.

인스턴스에서의 사용

let avante = new Car('hyundai', 'avante', 'black');
avante.color; // 'black'
avante.drive(); // 아반떼가 운전을 시작합니다~

let model3 = new Car('tesla', 'model3', 'red');
model3.brand; // 'tesla'
model3.refuel(); //미니에 연료를 공급합니다~
개념설명
prototype모델의 청사진(class)을 만들때 쓰는 원형 객체(original form)
constructor인스턴스가 초기화될 때 실행하는 생성자 함수
this함수가 실행될 때, 해당 scope마다 생성되는 고유한 실행 컨텍스트
* new키워드로 인스턴스를 생성했을때에는 해당 인스턴스가 바로 this의 값이 됨

생성자함수, prototype 객체, instance

[ES5]방식

[ES6]방식으로 직접 작성해보기(해라!)

// 생성자 함수, 속성과 메서드 정의
class Car {
constructor(brand, name, color) {
this.brand = brand;
this.name = name;
this.color = color;
}
refuel() {
}
drive() {
console.log(this.name + '가 운전을 시작해요');
}
}
//인스턴스
let gv90 = new Car('hyundai', 'gv90', 'whtie');
gv90.color; //'white'
gv90.drive(); //'gv90가 운전을 시작해요'

예제 - 배열

//우리가 배열을 정의하는 것은 Array의 인스턴스를 만들어내는 것과 동일합니다.
let arr = ['apple', 'banana', 'oragne']
let arr = new Array('apple', 'banana', 'oragne');

mdn 문서를 보면 배열 메서드는 Array.prototype.메서드명 과 같이 안내 돼 있음
이는 모든 메서드들이 클래스의 원형 객체(prototype)에 정의되어 있기 때문임.
아래와 같이 Array라는 클래스가 정의 돼 있는 거라고 이해함

 class Array {
   constructor(length) {
   	this.length=...}
    
   push(){
     push메서드구현하는코드
   }
   pop(){
   }
   map(){
   }
   filter(){
   }
   reduce(){
   }

실습 - 클래스 문법으로 카운터 만들기

class Counter {
   constructor() {
      this.value = 0; //생성자 호출을 할 경우, this는 new키워드로 생성한 Counter의 인스턴스입니다.
    }
    increase() {
      this.value++
    }
    decrease() {
      this.value--
    }
    getValue() {
      return this.value
    }
 }
 
 let counter1 = new Counter() //생성자 호출
 counter1.increase()
 counter1.getValue() //1

1-3. 객체 지향 프로그래밍

객체 지향 프로그래밍이라는 패러다임이 등장하기 전, 먼저 절차 지향 프로그래밍이 있었습니다.

절차적 언어

  • 초기의 프로그래밍 언어( C, 포트란 등)
  • 객체 지향의 개념이 없는 순차적인 명령의 조합

그러나, 객체 지향 프로그래밍이라는 패러다임이 등장하면서, 단순히 별개의 변수와 함수로 순차적으로 작동하는 것을 넘어, 데이터의 접근과, 데이터의 처리 과정에 대한 모형을 만들어 내는 방식을 고안해냈습니다.

따라서, 데이터와 기능이 별개로 취급되지 않고, 한 번에 묶여서 처리할 수 있게 됨

객체 지향 언어

  • "클래스"라고 부르는 데이터 모델의 청사진을 사용해 코드 작성
  • 현대의 언어들은 대부분 객체 지향의 특징을 갖고 있음 (대표적으로 Java, C++, C# 등)
  • 자바스크립트는 엄밀히 말해 객체 지향 언어는 아니지만 객체 지향 패턴으로 작성 가능

OOP

  • OOP는 프로그램 설계 철학 중 하나

  • OOP는 객체로 그룹화 됨

    • 이 객체는 한번 만들고 나면, 메모리상에서 반환되기 전까지 객체 내의 모든 것이 유지됩니다.
  • OOP의 4가지 주요 개념을 통해 재사용성을 얻을 수 있음

  • 객체 내에는 "데이터와 기능이 함께 있다"라는 원칙에 따라 메서드와 속성이 존재합니다.

    • 예를 들어, 모든 자동차는 공통적인 기능과 고유의 속성이 있습니다. 속도를 낸다든지, 주유를 한다든지 등의 기능이 존재하며, 속성으로는 색상, 최고 속력 혹은 탑승인원 등과 같은 고유의 데이터가 존재함
    • 새로운 객체를 만들 때는, "이번에 만들 자동차는, 빨간색의 최고 속력은 200km/h를 내도록 만들어보자!"와 같이, 속성에 고유한 값을 부여할 수 있습니다.

클래스와 인스턴스

  • 클래스는 일종의 원형(original form)으로, 객체를 생성하기 위한 아이디어나 청사진입니다.
  • 인스턴스는 클래스의 사례(instance object) 입니다.
  • 클래스는 객체를 만들기 위한 생성자(constructor) 함수를 포함
  • 클래스는, 세부 사항(속성)이 들어가지 않은 청사진입니다.
    • 세부 사항만 넣는다면, 객체가 되는 것입니다
    • 클래스를 통해 만들어진 객체를 특별히 인스턴스 객체, 줄여서 인스턴스라고 부릅니다.
    • 생성자를 통해 세부 사항(속성)을 넣을 수 잇음. 함수에 인자를 넣듯, 속성을 넣을 수 있습니다.

객체 지향 프로그래밍의 주요 4가지 개념

애플리케이션을 만들 때 좋은 설계를 하기 위해서는, 기본적으로 이 객체지향을 이해하고 응용하는 것이 중요합니다.

Encapsulation (캡슐화)
Inheritance (상속)
Abstraction (추상화)
Polymorphism (다형성)

(1) Encapsulation (캡슐화)

  • 데이터와 기능을 하나의 단위로 묶는 것
  • 은닉(hiding): 구현은 숨기고, 동작은 노출시킴
  • 느슨한 결합(Loose Coupling)에 유리 : 언제든 구현을 수정할 수 있음
    • 데이터(속성)와 기능(메서드)을 따로 정의하는 것이 아닌, 하나의 객체 안에 넣어서 묶는 것입니다.
    • 데이터(속성)와 기능(메서드)들이 느슨하게 결합되는 것이죠.

느슨한 결합

코드 실행 순서에 따라 절차적으로 코드를 작성하는 것이 아니라,
코드가 상징하는 실제 모습과 닮게 코드를 모아 결합하는 것을 의미합니다.

예를 들어, 마우스의 상태를 속성(property)으로 정하고,
클릭, 이동을 메서드(method)로 정해서 코드만 보고도 인스턴스 객체의 기능을 상상할 수 있게 작성하는 것이 느슨한 결합을 추구하는 코드 작성법입니다.

은닉화

캡슐화라는 개념은 "은닉화"의 특징도 포함하고 있는데, 은닉화는 내부 데이터나 내부 구현이 외부로 노출되지 않도록 만드는 것입니다.

따라서, 디테일한 구현이나 데이터는 숨기고, 객체 외부에서 필요한 동작(메서드)만 노출시켜야 합니다.

은닉화의 특징을 살려서 코드를 작성하면 객체 내 메서드의 구현만 수정하고, 노출된 메서드를 사용하는 코드 흐름은 바뀌지 않도록 만들 수 있습니다.

반면 절차적 코드의 경우 데이터의 형태가 바뀔 때에 코드의 흐름에 큰 영향을 미치게 되어 유지 보수가 어렵습니다.

(2) Abstraction (추상화)

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

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

추상화는 캡슐화와 비교해서 종종 헷갈려 하는 개념 중 하나입니다.
캡슐화가 코드나 데이터의 은닉에 포커스가 맞춰져있다면,
추상화는 클래스를 유저에게 필요하지 않은 메서드 등을 노출시키지 않고, 단순한 이름으로 정의하는 것에 포커스가 맞춰져 있습니다.
클래스 정의 시, 메서드와 속성만 정의한 것을 인터페이스라고 부릅니다. 이것이 추상화의 본질입니다.

(3) Inheritance (상속)

상속은 부모 클래스의 특징을 자식 클래스가 물려받는 것입니다.
자세히는 "기본 클래스(base class)의 특징을 파생 클래스(derived class)가 상속받는다"로 표현합니다.
그러나, 더욱 많이 쓰이고 있는 부모/자식이라는 용어를 사용합니다.

예를 들어, 사람(Human)이라는 클래스가 있다고 가정해 봅시다.
사람은 기본적으로 이름과 성별, 나이와 같은 속성, 그리고 먹다, 자다 등과 같은 메서드가 있다고 볼 수 있습니다.
추가적으로 학생(Student)이라는 클래스를 작성한다고 생각해 봅시다.
그런데 이때 앞서 구현했던 사람(Human) 클래스의 속성과 메서드를 다시 구현한다면 비효율적일 것입니다

학생의 본질은 결국 사람이므로, 상속을 이용하여 학생(Student) 클래스는 사람(Human) 클래스를 상속받을 수 있습니다.
학생은 추가적으로 학습 내용, 공부하다 와 같은 속성/메서드를 추가합니다.

(4) Polymorphism (다형성)

poly는 "많은", 그리고 morph는 "형태"라는 뜻을 가지고 있습니다.

"말하다"라는 동작의 본질은 "입으로 소리를 내다"를 의미합니다.
그러나, 각기 다른 동물들이 "말할 때" 제각각의 소리를 내는 것처럼,
객체 역시 똑같은 메서드라 하더라도, 다른 방식으로 구현될 수 있습니다.

html 엘리먼트를 여러분들이 직접 구현한다고 생각해 보세요.
모든 엘리먼트들은 전부 객체이므로, 내부적으로 모양을 그리고 화면에 뿌리는 메서드가 존재할 것입니다.
이 메서드가 render라는 이름을 갖고 있다고 가정해 봅시다.

TextBox, Select, Checkbox의 공통의 부모인 HTML Element라는 클래스에 render라는 메서드를 만들고 상속을 받게 만들 수 있습니다. 그런데 다형성의 핵심은 이 같은 이름의 render라는 메서드가 조금씩 다르게 작동한다는 데 있습니다.

TextBox는 가로로 긴 네모 상자와 커서가 있는 형태일 것이고, Select 박스는 눌렀을 때 선택지가 나오도록 화면에 그려야 할 것입니다. 이처럼 같은 이름을 가진 메서드라도 조금씩 다르게 작동합니다. 이것이 바로 다형성입니다.

OOP 주요 개념의 이점

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

OOP의 의미

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


1-4. 객체지향 차이점

은닉화(private 키워드)의 한계

Java나 TypeScript는 클래스 내부에서만 쓰이는 속성 및 메서드를 구분시키기 위해 private이라는 키워드를 제공

JavaScript에서는 은닉화를 돕기 위해서 일반적으로 클로저 모듈 패턴을 사용함
JavaScript는 private이라는 키워드가 없지만 ES2019부터 #이라는 키워드가 도입되었습니다.

추상화(interface 키워드)기능의 부재

Java나 TypeScript는 interface를 구현해 놓았으며 이 인터페이스가 일종의 규약처럼 간주되어, 클래스를 구현하는 사람들이 이에 맞게 작성할 수 있게 돕습니다.

또한, 이는 클래스를 이용하는 입장에서 노출된 인터페이스를 통해 "이 클래스는 메서드 이름이 의도한 바대로 작동할 것이다"라는 것을 명백히 드러나게 해 줍니다. 또한 실질적인 구현 방법을 공개하지 않고, 사용법을 노출시키기에도 유리합니다.

JavaScript에는 interface 키워드는 없습니다.

어떤 클래스가 외부 공개용으로 모듈처럼 작동할 때에, 이러한 인터페이스는 빛을 발합니다. 이러한 인터페이스 사용의 대표적인 예가 API(Application Programming Interface)

profile
중요한건 꺾이지 않는 마음이 맞는 것 같습니다

0개의 댓글