210226_TIL

김재헌·2021년 2월 26일
0
post-thumbnail

벌써 금요일이 된지도 몰랐다. 시간이 점점 빨리간다는 생각이든다. 오늘 프로토타입을 공부하다가 너무 이해가 안 가고 어려워서 이리저리 농땡이를 치고 있었는데 하체 운동이 가장 중요하다는 글을 보게 되었다. 그래서 유튜브에 '하체 운동'을 쳐보니 '하루 15분 하체 루틴'이라는 영상이 나왔는데 보니까 뭐 할만해 보였다. 그런데 댓글을 읽어보니 영상 제작자를 죽이고 싶은 마음이 다분해 보였다. 엄청 힘든가보다..
그래서 나는 그냥 하루에 자전거 5분정도 타는걸로 타협하기로했다.😋


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

객체 지향 프로그래밍(OOP)은 코드를 짜는데 도와주는 툴이나 다른 프로그래밍 언어가 아니다.
프로그래밍을 어떻게 할 것인가와 같은 스타일링에 가깝다.

그럼 어떤 스타일로 프로그래밍하는건데?
사람이 생각하는 현실세계와 비슷하게 프로그래밍 하는 방법이다. (이건 내가 직접 OOP를 해봐야 정확히 이해가 갈 것 같다)

OOP는 결국 객체를 만들고 객체 단위로 구분시켜 프로그래밍하는 방법이다.
객체는 객체가 어떠한 취지를 가지고 있냐에 따라
객체의 기능과 연관되어 있는 것들을 묶는 것이다.
그리고 서로 연관이 없는 로직과 구분지어주면 그것이 OOP이다.

같은 취지를 가지고 있는 데이터로만 묶여있기 때문에
결국 다른 프로그래밍에서도 같은 취지의 객체를 사용할 수 있다.
즉, 코드를 재사용할 수 있다.

예를 들어, 자동차는 여러가지 부품이 모여 하나의 자동차를 이룬다.
부품 중에는 핸들, 타이어, 계기판, 엑셀레이터 등이 있다.
이러한 부품이 하나의 객체라고 생각하면 편하다.
앞서 말했듯이 객체는 같은 취지를 가지고 있는 데이터의 묶음이듯
핸들은 방향전환을 하기 위해 바퀴를 회전시키는 취지,
타이어는 자동차를 움직일 수 있도록하는 취지,
계기판은 사용자에게 여러가지 정보를 전달하는 취지를 가지고 있다.
이렇게 서로 다른 취지를 가지고 있는 부품이 한데 모여 하나의 자동차
개발의 관점에서 보면 하나의 프로그램이 만들어진다.

만약 타이어에 문제가 있어 정비소에 가면 자동차 전체를 수리하는 것이 아니라
타이어만 수리하거나 교체해주면 문제가 해결 된다.
프로그래밍에 있어서도 Tier라는 객체에 문제가 있다면 그 객체 하나만 수정하면 되고
프로그램 전체를 수정 할 필요가 없다.

그렇다면 운전자가 핸들이나 엑셀레이터를 사용할 때 핸들은 어떠한 로직에 의해서 바퀴를 움직이고
엑셀레이터는 어떠한 로직으로 자동차를 전진시키는지 아는가?
전문가가 아니라면 알지 못 할 것이다.
그렇기 때문에 캡슐화가 필요하다.

캡슐화

현관에서 초인종이 울리고 아주 기쁜 마음으로 택배를 받았다.
설레는 마음으로 박스를 조심스럽게 뜯으면서 이번에 새로 시킨 게임기를 영접했다.
그런데 게임기를 보는 순간 도대체 어떻게 사용하는지 모르겠고 설명서를 봐도 이해하기 어렵다.
이런 제품은 캡슐화를 실패했다고 볼 수 있다.

캡슐화는 어떠한 로직이 어떻게 구현되는지는 숨기고 어떻게 동작하는지만 노출시키는 방법이다.
A키를 누르면 공격하고, B키를 누르면 점프하고, C키를 누르면 앉는 게임기가 있다고 해보자.
사용자는 A키를 누르면 자신의 캐릭터가 공격을 하는지 알고 있다.
하지만 어떠한 로직에 의해서 공격이 되는지 생각하면서 게임하는 사용자는 없다.
이렇듯 게임 캐릭터가 어떻게 공격을 하는지 방법은 객체안에 숨기고
사용자가 캐릭터를 공격하는 방법만 알게끔 노출하는 것이 캡슐화이다.

추상화

현실 세계는 매우 복잡하다.
이러한 복잡한 현실을 사용자의 관심사만을 추출해낸 것이 추상화이다.

추상화에 대해 공부하면서 문뜩 자동차 게임 '카트라이더'가 생각났다.
게임 관련 유튜브를 자주 보는데 한 영상에서 '카트라이더'는 게임을 단순화시켜서 대박난 게임이라고 소개했었다.

현실 세계에서 자동차가 작동하는 원리는 일반인이 이해하기 어렵다.
하지만 '카트라이더'에서는 키보드의 방향키 ↑를 누르면 앞으로 간다.
shift + ↑←를 누르면 드리프트라는 고급 기술까지 사용할 수 있다. (현실에서 자동차를 드리프트 한다고 생각해보자)

현실 세계의 자동차로부터 관심 있는 특성인 전진, 후진, 좌회전, 우회전 등을 모아 게임 속 '카트'를 만든 것이다.
즉, 자동차를 추상화해 '카트'를 만들었다고 할 수 있다.
이렇게 플레이어가 현실과 다르게 아주 접근하기 쉽게 단순화시키는 것을 추상화라고 생각해보았다.

예시로 맞을지 모르겠지만 모든 종류의 모니터가 추상화의 좋은 예시라고 생각된다.
컴퓨터가 작동하는 로직은 매우 복잡하지만 사용자는 모니터를 통해 원하는 것만 보고 손 쉽게 조작할 수 있다.

Prototype

프로토타입..프로토타입..프로토타입..프로토타입..
PTSD가 올 것 같다.

prtotype을 쓰는 이유는
class를 상속시키거나
상속시킨 class에 조건을 추가해 사용하고 싶을 때 사용하는 것 같다.

모든 객체는 함수로 만들어 진다.
원래 객체를 만들 때

let obj = {}; 

이런식으로 만들었다.
하지만 사실

let obj = new Object();

이 것의 축양형 느낌이었다.
어디서 많이 본 것 같다.
바로 class의 instance를 생성할 때 봤던 코드다.
결국 Object클래스의 obj라는 instance를 생성한 것이다.

그럼 Object가 무엇인가.
Object 또한 class이며 function이다.

JS의 모든 객체는 Object의 자손이다.
이 말 뜻은 JS의 모든 객체는 Object에 상속되어 있고
Object의 속성, 메서드를 사용할 수 있다는 것과 같다.

상속성, 다형성

Game이라는 class를 만들어보자

let Game = function(name, company, price) {
           // 속성
           this.name = name;
           this.company = company;
           this.price = price;
}
// 메서드
Game.prototype.run = function() { // Game에 run이라는 메서드 추가
	console.log(`${this.name}가(이) 실행됩니다`)
};

모든 Game은 실행을 시켜야 한다.

그럼 Racing이라는 class를 만들어보자

let Racing = function(name, company, price) {
  Game.call(this, name, company, price) // 부모 클래스(Game)로 this를 넘겨준다
    // Game(name, company, price)
    //  this.name
    //  this.company
    //  this.price
}

Racing게임 또한 실행시켜줘야하기 때문에 Game을 상속받자

Racing.prototype = Object.create(Game.prototype); // Game의 프로토타입을 기반으로 Racing의 프로토타입을 만든다
// 이거 안해주면 부모 클래스 메서드 사용 못함
Racing.prototype.constructor = Racing; // constructor(생성자)를 명확하게 설정해줘야 한다.
// 왜? JS는 원래 OOP를  생각하고 만들어진 언어가 아니기 때문
// 자식 클래스로 만들어준 인스턴스의 __proto__를 부모 클래스가 아닌 자식클래스로 바꿔주는 역할

레이싱 게임에는 경주 맵이 필요하기 때문에 경주 맵을 로드시키자

Racing.prototype.loading = function() { // Racing에는 Game에 없는 조건 추가(다형성)
	console.log('경주 맵을 로드합니다')
}

그리고 kartRider는 레이싱 게임이니까 Racing함수로 만들고

let kartRider = new Racing('KartRider', 'NEXON', 'Free to Play')

앞에서 Racing은 Game을 상속받았으니까
run()도 실행이 될까?

kartRider.name // 'KartRider'
kartRider.run() // 'KartRider가(이) 실행됩니다'
kartRider.loading() // '경주 맵을 로드합니다'

run()이 실행 된다!
loading()은 앞서 Racing을 만들면서 추가한 조건이다.
이렇게 부모 클래스에서 상속받은 속성, 메서드와 함께 다른 조건을 추가해서
조금 다른 방식으로 구현될 수 있는 것을 다형성이라고 한다.

kartRider라는 객체는 Racing이라는 class를 통해 만들어졌다.
그런데 어떻게 Game의 메서드(run())를 사용할 수 있을까?
바로

Racing.prototype = Object.create(Game.prototype);

이렇게 Racing을 Game의 protyotype을 기반으로 생성해 상속시켜주었기 때문이다.

그렇다면 Game은 어디에 상속되어 있을까?
앞서 말했듯 Object에 상속되어 있다.

prototype이 뭐길래 이런 코드 구현이 가능할까?
그 이유는 함수를 정의하면 함수만 생성되는 것이 아니라 Prototype Object도 같이 생성된다. (여기서 거의 모든 의구점이 풀린다)

Racing.prototype = Game.prototype은 왜 안될까?
참조를 바꿔버려서
Game에까지 영향

Prototype Object


출처:https://medium.com/@bluesh55/javascript-prototype-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-f8e67c286b67

함수가 생성이 되면 Prototype Object도 같이 생성된다.
그리고 prototype이라는 속성을 통해 Prototype Object에 접근할 수 있기 때문에 상속이 가능한 것이다.
그리고 Prototype Object는 일반적인 객체이므로 속성을 마음대로 추가하거나 삭제할 수 있다.

Racing.prototype.loading = function() {
	console.log('경주 맵을 로드합니다')
}

prototype이 껴있어서 낯설게 느껴지지만
prototype을 없애면..

Racing.loading = '경주 맵을 로드합니다'

어디서 본 적이 있지 않은가?
객체에 속성을 추가할 때 썼던 '닷노테이션' 형태이다.
결국 Racing.prototype.loading은
함수로 객체를 생성할 때 같이 생성된 Prototype Object에 속성을 추가하겠다는 말이다.

kartRider는 Racing을 통해 생성되어서
Racing.prototype을 참조할 수 있게 된다.

function Game() {}
Game.prototype.name = 'KartRider' // prototype공간에 name: 'KartRider' 저장
Game.prototype.company = 'NEXON'

let kartRider = new Game();
kartRider.name // 'KartRider'

kartRider는 Game을 통해 만들어졌기 때문에 name이라는 속성이 없어도
kartRider.name을 치면
'KartRider'가 나온다.

이것을 가능하게 해주는 열쇠가 __proto__이다.

__proto__속성은 모든 객체가 빠짐없이 가지고 있는데
객체가 생성될 때 부모였던 함수의 Prototype Object를 가리킨다. (Game)
그럼 Game의 __proto__는 Game의 부모인 Object를 가리킨다.
그럼 Object의 __proto__는 ?
null이다!

Prototype Chain

객체의 속성을 찾을 때 __proto__를 통해 원하는 속성을 찾을 때 까지 상위 prototype을 탐색한다.
이렇게 __proto__속성을 통해 상위 prototype과 연결되어 있는 형태를 Prototype Chain이라고 한다.

ES6 문법

여태까지 instance를 만드는 과정이 굉장히 번거롭고 복잡하게 느껴졌다.
그래서 새로운 문법으로 class가 나온 것이다.
ES6에서 새로 추가된 문법인데 앞에 함수로 class를 작성한 원리를 그대로 따르지만 단지 문법적 편의만 제공한다.
class를 사용한 코드는 이렇다.

class Game {
	constructor(name, company, price) { // 생성자 권한을 부여해준다
    	this.name = name;
        this.company = company;
        this.price = price;
    }
    run() { // 메서드도 따로 prototype을 통해 설정해줄 필요 없다
      console.log(`${this.name}가(이) 실행됩니다`)
    }
}
class Racing extends Game { // 부모 class로 Game을 두고 있다 즉, Game에 상속되어 진다
	constuctor(name, company, price) { // 같은 인자를 받아 같은 값으로 쓰고 싶다면 생략 가능하다
    	super(name, company, price) // 부모 class의 this와 연결
        this.name = name;
        this.company = company;
      	this.price = price;
    }
   loading() { // 동일한 조건이지만 또 다른 조건을 추가하고 싶을 때 상속한다
     console.log('경주 맵을 로드합니다')
   }
}
class Racing extends Game{
  // 위의 class와 완전히 같다
  // 간결한 코딩이 가능하다
  loading() { // 추가 된 조건
    console.log('경주 맵을 로드합니다')
  }
}
let kartRider = new Racing('KartRider', 'NEXON', 'Free to Play')
kartRdier.name // 'KartRider'
kartRider.company // 'NEXON'
profile
개발자되려고 맥북샀다

0개의 댓글