객체지향 프로그래밍과 javascript (약간의 역사를 곁들인...)

teo·2022년 3월 3일
92

테오의 프론트엔드

목록 보기
19/35
post-thumbnail

CS로 보는 객제 지향 프로그래밍 그 자체의 개념이 뭔지 궁금하네요.

자바스크립트의 class는 다른 언어와의 class는 다르잖아요..
그렇지만 가장 일반적인 객체지향 언어인 Java에서의 객체지향과 자바스크립트의 객체지향의 관점은 같은 걸까요?

자바스크립크트서 객체 지향을 하는게 맞나요?
언어를 객체지향 패러다임에 끼워 맞추기 위해서 한 것이라 들었는데...
자바스크립트에서 객체는 함수로 되어 있는데 객체 = 함수라면 함수형 프로그래밍을 해야 되는 걸까요?

이번 글에서는 객체지향 프로그래밍에 대해 이야기를 해보려고 합니다. 그리고 자바스크립트의 객체지향은 일반적인 객체지향 프로그래밍과는 어떻게 다른지 그리고 javascript에서는 객체지향 프로그래밍을 어떻게 해야하는게 좋을지 한번 이야기 해보고자 합니다. (이번 블로그 소재를 제공해준 단테에게 고맙다는 말을 전합니다.)

프롤로그

프로그래밍 세계에서 무언가를 학습하려고 할 때에는 그 이론 자체를 이해하기보다는 먼저 그 기술이 나오게 된 배경을 이해하는 것이 굉장히 중요합니다. 그래야 새로운 변화구를 맞이하였을때 맥락에 맞게 내 것으로 이해를 하기 쉬워지기 때문입니다.

프로그램 뿐만 아니라 기술의 발전은 대략 다음과 사이클로 돌아가게 됩니다.


첫번째 시기: 문제 인식

프로그램을 하다 보면 누구나 한번쯤 겪을 법한 문제 인식들이 쌓이기 시작하는 단계입니다. 그러면 이러한 문제를 해결하기 위해서 저마다의 여러가지 해법들을 찾아보게 됩니다. 문제 인식에 대한 공감대는 합의가 되었지만 이 문제를 해결하기 위한 최선의 방법은 아직 등장하지 않는 시기입니다.

두번째 시기: 명명된 새로운 기술의 등장

여러가지 해법들이 쌓여가다보면 모두가 합의할만한 이론이나 기술이 등장하고 이러한 문제 인식과 해법에 대해 이름이 붙여지는 시기 입니다. 그리고 이로인해 대부분의 사람들이 명확하게 이 문제와 해결방법에 대해서 인식을 하게 됩니다. 여기서 중요한 사실은 대부분 컨텍스트와 해결 방법은 이미 먼저 나온 상태에서 이것들에 대해 정리된 이론이나 개념들은 나중에 이름 붙여진다는 사실입니다. 이때 보통 패러다임이 전환이 되었다라고들 하게 되죠.

세번째 시기: 기술의 부흥

이러한 패러다임의 전환으로 인해 이러한 이론과 개념들 그리고 문제 해결 방법이 정립이 되고 나면 이후 발생하는 모든 문제들은 새로운 시각인 이 방법을 가지고 해결해보려는 시도들이 발생하게 됩니다. 그러면서 개념과 기술이 폭발적으로 성장하는 시기를 겪게 됩니다. 기존의 문제들이 세련된 형태로 대부분 해결이 되며 이 기술을 당연한 것으로 배우게 되는 시기입니다.

네번째 시기: 안정기(혹은 정체기)

패러다임 전환으로 폭발적인 변화의 시기를 통해 성장을 하고나면 새로운 한계점을 마주치게 됩니다. 기존의 방식으로 만들어진 문제점들이 그 패러다임으로는 해결이 되지 않는 문제점들을 발견하게 되며 기존 방식으로 이를 해결을 하려다보면 굉장히 복잡해 보이는 방식으로 해결해야 한다는 사실을 알게 됩니다. 이러면서 문제인식을 가지고 다시 첫번째 시기가 되어 기술 발전의 사이클이 돌아가게 됩니다.


위 내용을 React에 대입해도 좋고 컴퓨터나 스마트폰에 대입해도 좋습니다. 그렇게 알고 있는 기술에 빗대어 한번 곱씹어 보길 바랍니다. 대부분의 기술 발전은 위와 같은 사이클을 가지게 되어 있습니다.

우리가 프로그래밍을 하다보면 만들어지는 개념들이나 배워야 하는 것들은 대부분 세번째 시기 때에 만들어지는 응용법들을 배우고 학습하게 됩니다. 이러한 맥락과 해결방법은 자연발생한 결과물인데 이러한 결과물에 이름을 붙이고 이론을 다듬는 과정에서 만들어진 생소한 용어와 정의 그리고 개념을 맥락 없이 이해하려면 상당히 어렵습니다.

결국 기술이란 지난 문제들을 해결하기 위해서 만들어졌기 때문에 기술에 대한 용어나 개념의 이해가 없이도 왜 이러한 기술이 필요했고 어떠한 발전 과정을 거쳤는지를 이해를 한다면 새로운 용어에 대한 정의나 개념의 이해가 더 쉽게 될 것입니다.

이번에 적어 볼 이야기는 객체 지향 프로그래밍입니다. 위와 같은 흐름에 맞춰 설명을 하려고 하니 한번 다시 리마인드를 하고 읽어 주시면 좋겠네요.

객체지향 프로그래밍에 대한 간단한 역사와 내용 그리고 자바스크립트에서는 이러한 전통적인 객체지향과는 어떠한 차이점이 있는지에 대해서 한번 알아봅시다.

주의: 앞으로 객체지향의 용어와 개념을 자연스럽게 설명하기 위해서 객체지향 프로그래밍의 발전과정이 마치 선형적으로 진행된 것 처럼 풀어나갈 것이나 실제로는 그렇지 않다는 점 그래서 다소 비약이 있을 수 있다는 점 미리 알려드립니다.


객체지향 이전의 이야기

객체지향 패러다임이 존재하기 전으로 가봅시다. 바로 상상하기는 쉽지 않을테니 이 글을 읽는 사람은 최소한 자바스크립트를 알고 있다는 가정 하에 javascript에서 Object를 쓰지 않고 boolean, string, number와 if, for, while 만으로 개발을 한다고 한번 상상 해 보세요.

(예시 코드는 그 시절 언어보다는 이해하기 편하게 javascript로 통일하였습니다.)

순차적 프로그래밍과 goto

완전 초창기 프로그래밍에서는 프로그램은 순서대로 실행이 되었습니다. 지금도 프로그래밍을 하다보면 그전에 만들어 둔 것과 같은 반복적인 동작이 필요하다는 것을 알게 됩니다. for나 while가 같은 반복문이 존재했지만 일부 반복의 범위가 코드를 다시 실행을 해야 할 경우가 발생을 했고 함수라는 개념이 없던 시절에는 특정 위치로 실행 순서를 강제로 변경하는 goto문을 만들어내게 됩니다. 이렇게 강제로 실행 순서를 바꾸다 보니 코드가 커져가면서 이 코드의 흐름을 제어하기가 힘들어지고 무언가 방법을 찾게 됩니다.

var hp = 100
var mp = 100

gameloop:
...
if (key == 'A') {
  goto magic 
}
...
goto gameloop

magic:
mp -= 10
...
goto gameloop

절차적(구조적) 프로그래밍

그 이후 실행순서를 강제로 바꾸는 것이 아니라 일정하게 반복되는 코드를 따로 만들어두고 해당 하는 코드를 호출하고 나서 다시 원래 자리로 돌아오는 방식의 프로시저(함수)를 통해서 개발을 하는 절차적 프로그래밍 패러다임이 탄생하면서 지금의 함수와 같은 개념이 생겼습니다.

즉 절차적 프로그래밍은 데이터와 데이터를 처리하는 동작을 함수단위로 코드를 분리하고 재사용하는 형태로 프로그래밍을 하는 방식이 됩니다. (함수라는 용어를 쓰기는 하지만 함수형 프로그래밍 하고는 다릅니다.)

절차적 프로그래밍은 우리에게 아주 익숙한 방식입니다. 현재에도 간단한 코드들을 작성을 할 때에는 이러한 방식으로 프로그래밍을 하고 있습니다.

언제나 문제는 코드의 덩치가 커질때 발생한다.

패러다임의 한계는 프로그램의 덩치가 커져야 알 수가 있습니다. 이러한 방식으로 코드가 커지게 되면 다음과 같은 문제가 발생했습니다.

일단 기본적으로 절차적 프로그래밍에서는 전역 변수의 형태로 만들었습니다. 그러다보니 프로그램의 덩치가 커지면 커질수록 변수에 같은 이름을 쓸 수가 없게 됩니다. 그러다 보니까 이 변수명 관리가 굉장히 복잡해지게 됩니다.

그러다보니 이름 앞에 foo_x, foo_y, bar_somthing과 같이 prefix가 늘어만 가고 매번 이렇게 prefix를 붙이지 않고 하나의 파일단위 혹은 모듈단위로 prefix를 부여해서 관리하는 namespace(네임 스페이스) 라는 방식이 등장을 합니다.

데이터를 묶어서 관리해보자! = 구조체

그러나 namespace만으로는 비슷한 형태의 데이터들을 여전히 쉽게 다룰 수는 없었습니다. 가령 게임을 만든다고 가정해보면 하나의 캐릭터에 속해있는 이름, hp, mp, item 등 구조의 형태를 가지는 변수를 만들기 위해서는 여전히 prefix를 붙여서 만들어야 했습니다.

var character1_name = "teo.yu"
var character1_hp = 300
var character1_mp = 500

function character1_useSkill() {
  ...
  character1_mp -= 100 // 변수를 직접 수정하게 됨.
}

character1_do_somthing()

위와 같은 식으로 프로그래밍을 하게 될 경우 캐릭터가 2개, 3개만 되어도 만들어야 할 코드와 중복될 내용이 눈에 선하죠. 그래서 이렇게 서로 연관이 있는 데이터들을 하나로 묶어서 namespace처럼 관리하여 해당 변수에 접근을 할 수 있는 구조체라는 형식을 생각하게 됩니다.

// struct
// (사실 구조체는 엄밀히 말해 하나의 Type인데 js라서 그냥 이렇게 쓰겠습니다. 찰떡같이 이해해주세요.)
var character = {
  name: "teo.yu"
  hp: 300
  mp: 500
}

function useSkill(character) {
  ...
  character.mp -= 100 // 변수를 직접 수정하게 됨.
}

do_somthing(character)

이를 통해서 의미있는 단위로 변수들을 하나로 묶음으로써 변수명의 중복을 줄이고 함수나 배열등에서도 하나의 변수처럼 활용할 수 있게 되면서 코드가 덩치가 커져도 일관성을 유지하면서 코드를 짤 수 있게 되었습니다.

여기까지의 개념으로 만들어진 언어중에서 가장 유명한것이 바로 C언어 입니다.

struct 위키백과
https://ko.wikipedia.org/wiki/Struct
js는 구조체가 없는데 굳이 c언어로 설명을 하려다보면 괜히 복잡해 질 것 같아서 링크만 남겨둡니다.


객체 지향 프로그래밍의 등장

구조체가 생기면서 산재해 있는 데이터들를 의미있는 데이터로 구조화 시켜서 프로그래밍을 하니 동작보다는 데이터를 중심으로 코딩을 하게 되면 코드의 덩치가 커져도 일관성을 유지하기 좋다는 것을 깨닫게 됩니다.

그러면서 코드를 한데 모으다 보니 다음과 같은 패턴이 자주 만들어진다는 것을 알게 됩니다.

// struct
var character = {
  name: "teo.yu"
  hp: 300
  mp: 500
}

function character_attck(character) {...}
function character_useSkill(character) {...}
function character_moveTo(character, toX, toY) {...}

위와 같이 특정 구조체만 가지고 동작을 하는 함수군들이 만들어진다는 것을 알게 되었고 함수 역시 전역네임스페이스를 쓰고 있다보니 character_와 같은 prefix를 달아야한다는 것을 알게되었습니다.

그러면 구조체에 항상 쓰이는 함수들도 하나로 합치는 것은 어떨까? = class

그래서 구조체와 항상 쓰이는 함수들을 하나로 묶어서 구조체와 함께 함수까지 포함하는 개념을 만들게 되고 이를 class 라고 불렀습니다.

struct + function(struct, ...) = class
(구조체 + 구조체를 항상 인자로 가지는 함수 = 클래스!)

// class
class Character {
  name = "teo.yu"
  hp = 300
  mp = 500

  attck() {...}
  useSkill() {...}
  moveTo(toX, toY) {...}
}

// object
var character = new Character();
character.attck();
character.useSkill()
character.jump();                               

이렇게 만들고 보니 기존의 데이터와 처리방법을 분리해서 개발하던 절차식 프로그래밍과 달리 데이터와 처리방식이 하나의 모듈로 관리를 하게 되면서 마치 작은 프로그램들이 독립적으로 돌아가는 형태 를 띄게 되어 덩치가 큰 프로그래밍을 작성을 하더라도 하나의 목적을 가진 큰 프로그램을 만들어가는 것이 아니라 작은 부품들을 미리 만들어두고 이를 조립하고 결합하는 방식 으로 개발을 할 수 있다라는 것을 알게됩니다.

그렇다면 이러한 부품을 만드는 설계도를 만들어두고 공장에서 찍어내듯이 부품을 만들고 이것들을 조립을 하는 것과 같은 개념으로 classobject가 등장을 합니다.

기존의 구조체와 함수를 합쳐서 선언하는 것을 Class 라고 부르기로 했고 Class를 통해 만들어진 결과물을 동작 을 함께 가지고 있는 것이 주위 사물과 유사하다고 하여 Object 라고 부르기로 했습니다.

이런식으로 작은 문제를 해결하는 것들을 모아서 하나의 문제를 해결하는 프로그램으로 개발하는 방식을 Bottom-up 방식이라고 하여 이렇게 작은 문제를 해결하는 독립된 객체를 먼저 만들고 조립하자는 개발방식은 다음과 개념이 확장이 되게 됩니다.

프로그램은 모두 객체로 만들어져있고 객체들간의 메시지를 주고받는 상호작용으로 이루어진다.

이렇듯 프로그램을 객체로 바라보는 관점으로 프로그래밍을 하는 것을 Object-Oriented Programming (OOP) = 객체지향 프로그래밍 이라고 부르게 되었습니다.

독립된 객체를 조립해서 사용하는 방식은 곧 레고와 같이 재사용이 가능한 객체들을 많이 만들어 놓는 것이 중요하다는 것을 알게 되고 객체의 재사용을 높이기 위해 OOP에는 아래와 같은 여러가지 개념들이 추가되었습니다.

외부에서 알 필요 없는 것들은 숨겨놓자! - 캡슐화

작은 문제를 해결하는 독립된 객체를 사용하게 되면서 객체의 모든 데이터에 접근을 해야 할 필요가 없다는 것을 알게 되었습니다. 내부의 데이터는 내부에서 알아서 조작을 할 수 있도록 하고 외부에서는 필요한 내용만 만들어 두는 편이 프로그램의 안정성과 사용성 측면에서 낫다라는 것을 알게됩니다.

그래서 꼭 외부로 노출해야 하는 값과 내부에서만 사용하는 값을 구분하는 기능을 추가하도록 합니다. 이를 데이터를 보호해주는 캡슐과 같은 것으로 보고 내부 데이터에 바로 접근을 하지 못하게 하고 필요한 메소드만 열어두는 특성을 캡슐화 라고 부릅니다.

객체지향 프로그래밍에서는 이러한 캡슐화를 객체의 안정성을 높이고 필요한 메소드만 열어둠으로써 객체의 재사용성을 높일 수 있도록 하였습니다.

// class
class Character {
  name = "teo.yu"
  #hp = 300
  #mp = 500

  attck() {...}
  useSkill() {... this.#mp -= 50; ... }
  moveTo(toX, toY) {...}
}

// object
var character = new Character();
character.name = "테오" // public한 필드는 외부에서 수정이 가능한 잠재적 위험이 있다!

// private을 이용하면 mp를 외부에서 함부러(?) 수정할 수 없게 됩니다!
character.mp = 3000 // Error TS18013: Property '#mp' is not accessible outside class 'Human' because it has a private identifier.              

은닉을 향한 자바스크립트의 여정
https://ui.toast.com/weekly-pick/ko_20200312
자바스크립트에서 OOP의 캡슐화는 어떻게 될까? 자세한 내용은 이 링크를 한번 읽어봐주세요 :)

객체의 일부분만 재사용은 어떻게 해야 되지? - 상속!

객체가 중심이 되어 객체를 재사용을 하는 것은 좋은데 객체는 여러개의 변수와 여러개의 함수가 섞여 있다보니 일부는 재사용을 하고 일부는 달라져야 하는 경우가 빈번하다는 것을 알게 됩니다. 그래서 객체의 일부분만 재사용을 하는 방법이 필요하다는 것을 알게 되었습니다.

개발에서 절대 하면 안 될 일이 스스로 코드를 반복해서 작성하는 행위입니다.
DRY! - Do not Repeat Yourself!

그래서 이러한 반복을 안 하기 위해서 어떤 방법을 생각을 했을까요? 스타크래프트를 한번 만든다고 상상 해 봅시다. 우리는 열심히 코딩을 해서 저글링 클래스를 만들었습니다.

// class
class Zergling {
  name = "저글링"
  hp = 35

  die() {...}
  attck() {...}
  moveTo(toX, toY) {...}
}

그리고 이제 히드라를 만들어보려고 합니다. 그런데 히드라를 만들다 보니 저글링과 동일한 로직이 너무 많습니다. hp가 0이면 죽고 땅으로 이동하는 알고리즘도 동일합니다. 하지만 모션도 다르고 공격방식도 다르고 hp도 다르죠.

그래서 객체에서 공통된 부분만 따로 만들어서 그 코드를 같이 상속 받아서 활용을 하고 나머지 달라지는 것들만 각자 코드를 작성하는 방식으로 만들면 어떨까하는 생각을 하게 됩니다.

상속을 받을 수 있는 객체 이름을 따로 지어줘야겠다! - 추상화

그렇다면 객체에서 공통된 부분의 코드를 어떻게 만들고 어떻게 이름을 붙여줘야 할까요?

class Unit {
  name = ""
  hp = 0

  die() {...}
  attck() {...}
  moveTo(toX, toY) {...}
}

class Zergling extends Unit {
  name = "저글링"
  hp = 35

  attck() {...}
}

class Hydralisk extends Unit {
  name = "히드라리스크"
  hp = 70

  attck() {...}
}           

저글링도 히드라리스크도 함께 포함되고 있는 속성을 함께 가지고 있을 이름을 찾다보니 Unit이라고 부르면 좋을 것 같네요. 우리가 사과, 바나나를 한데 모아 과일이라고 부르듯이 공통적인 부분을 모아서 상위의 개념으로 새롭게 이름을 붙이는 것을 추상화라고 합니다.

이렇게 상속추상화 를 통해서 객체의 일부분을 재사용을 하는 방법을 찾게 되었습니다.

우리는 다 같이 움직이지만 각자의 방식으로 움직여요! - 다형성

이렇게 해서 상속과 추상화를 통해서 객체를 만들면 어떤 점이 좋아질까요?

스타크래프트에서 드래그 해서 저글링히드라, 뮤탈리스크, 오버로드를 함께 섞어 유닛들을 선택한 다음에 어디로 이동을 하라고 하는 내용을 구현한다고 생각해보겠습니다.

저글링과 히드라는 걸어서 이동하고 뮤탈리스크와 오버로드는 공중으로 이동 을 할 것입니다. 각자 이동하는 속도나 방법도 제각각이죠. 하지만 우리는 이 모든 것들이 하나의 이동이 가능한 유닛 으로 취급하여 같은 타입으로 취급을 할 수 있게 됩니다.

let zergling1 = new Zergling()
let zergling2 = new Zergling()
let hydralisk1 = new Hydralisk()
let hydralisk2 = new Hydralisk()
let mutalisk = new Mutalisk()
let overload = new Overload()

let units = [zergling1, zergling2, hydralisk1, hydralisk2, mutalisk, overload]

// 모두 같은 이름의 moveTo 메소드를 호출하지만 각자의 방식대로 동작한다.
units.forEach(unit => unit.moveTo(300, 400))

이렇듯 추상화된 유닛이라는 타입은 저글링히드라, 뮤탈리스크, 오버로드 등 하위 타입인 여러가지 타입으로 참조할 수 있다는 개념이 바로 다형성 입니다.

상속추상화 그리고 같은 Unit의 메소드를 사용하지만 각자 정의된 방식이 있다면 각자의 방식대로 동작 할 수 있도록 하는 다형성을 통해서 객체의 일부분만 재사용이 가능하도록 설계가 되었습니다.


객체 지향 프로그래밍의 발전

이렇게 객체지향 프로그래밍의 개념들이 정립이 되고 다듬어져 c++과 같이 객체지향 프로그래밍을 할 수 있는 언어들이 등장하기 시작했습니다.

객체지향 프로그래밍은 점차 주류 패러다임이 되며 완전 객체지향 패러다임을 지향하는 Java의 등장과 함께 객체 지향 프로그래밍은 프로그래밍 생태계에 주류 패러다임이 됩니다.

이제 프로그래밍을 할 때에는 객체 지향은 너무나도 당연한 것이고 "모든 것이 객체다", "객체간의 관계" 를 중요시하며 객체를 만들어내는 관점으로 프로그래밍을 바라보게 되었고 객체단위로 만들어지는 재사용 라이브러리나 모듈들로 인해서 기존 절차적 프로그래밍에 비해 훨씬 덩치를 키우기가 용이해졌고 독립적인 단위의 객체를 작게 만들고 조립한다는 개념은 엔터프라이즈 단위의 프로그래밍에서 훨씬 더 빛을 발하게 되었습니다.

그러나 객체지향은 언제나 복잡함 이라는 것을 가지고 있는 개념이기에 그냥 class와 object를 쓴다고 나아지는 것이 아니라 객체지향을 잘 하기 위해서는 객체들의 관계 를 잘 설정해야 하고 이로 인해 초기 설계에 굉장히 노력이 많이 들어가기에 객체지향 그 이상의 설계를 하는 원칙이나 방법들이 발전하게 되었습니다.

은빛총알은 없다. 객체지향도 잘 해야지! - S.O.L.I.D

이제 기존 프로그래밍 개발의 한계를 벗어나 훨씬 더 덩치가 큰 단위로 개발을 하다보니 객체지향을 쓴다는 것이 무조건 능사는 아니라는 것을 알게 되었습니다.

객체지향을 잘 하기 위해서는 class, object만 써서 되는게 아니라 여기에서도 하면 안되는 것들, 해야만 하는 것들이 발견되게 됩니다.

이러한 과정 속에서 S.O.L.I.D와 같은 객체지향을 잘 하기 위한 원칙들 같은 것들이 나오면서 객체지향의 의미를 살릴 수 있고 문제점들을 해결할 수 있다는 것들을 알게 됩니다.

이렇게 객체지향은 조금씩 더 발전을 하기 시작합니다.

SOLID가 궁금하신 분들은 제가 예전에 작성했던 글을 한번 읽어보세요 :)

Javascript에서도 SOLID 원칙이 통할까?
https://velog.io/@teo/Javascript%EC%97%90%EC%84%9C%EB%8F%84-SOLID-%EC%9B%90%EC%B9%99%EC%9D%B4-%ED%86%B5%ED%95%A0%EA%B9%8C

디자인 패턴의 등장

이렇게 개발 패러다임은 이제 완전히 객체지향 프로그래밍으로 넘어오게 되었습니다. 그리고 객체지향 패러다임으로 오랜동안 경험치가 쌓이다 보니 객체지향을 통해서 만들어지는 보편적인 설계 패턴들이 보이기 시작했습니다.

소프트웨어 디자인 패턴(software design pattern)은 소프트웨어 공학의 소프트웨어 디자인에서 특정 문맥에서 공통적으로 발생하는 문제에 대해 재사용 가능한 해결책이다. 소스나 기계 코드로 바로 전환될수 있는 완성된 디자인은 아니며, 다른 상황에 맞게 사용될 수 있는 문제들을 해결하는데에 쓰이는 서술이나 템플릿이다. 디자인 패턴은 프로그래머가 어플리케이션이나 시스템을 디자인할 때 공통된 문제들을 해결하는데에 쓰이는 형식화 된 가장 좋은 관행이다. - 위키백과

늘상 부딫히는 문제와 해결하기 위한 객체지향의 설계 그리고 예제코드등을 묶어서 하나의 패턴이라고 이름을 붙여놓고 서로 이러한 문제를 해결하는 관행처럼 사용을 한다면 훨씬 더 객체지향에 적합한 코드를 만들기가 쉬워질 것입니다. 이런식으로 만들어진 패턴 중 유명한 것들은 대략 20여개 정도가 존재합니다.

이러한 패턴들을 숙지하면서 적절한 문제에 적절한 설계구조를 만들게 된다면 다소 복잡할지언정 좋은 객체지향 프로그래밍 구조를 만들 수 있게 되었습니다. 우리가 보통 객체지향을 배운다고 하면 도달하는 종착지와 같은 곳입니다.

refactoring.guru | 디자인 패턴을 잘 정리한 사이트를 공유합니다
https://refactoring.guru/design-patterns


자바스크립트와 객체지향

저가요... OOP 디자인 패턴을 배웠거든요? 근데 javascript에서는 어떻게 해야할지 도저히 모르겠네요..

객체 지향 프로그래밍과 SOLID, 디자인 패턴등을 배우고 나면 마치 모든 프로그래밍을 객체 지향으로 할 수 있을 것만 같고 아주 효과적인 디자인 패턴을 통해서 멋진 설계를 가진 프로그램을 할 수 있을 것 같다는 생각이 듭니다. (과장이 아니라 실제로 그렇습니다.)

그리고 나서 자바스크립트를 만나게 되면 당황하게 됩니다. class는 어디있지? (- 그래서 지금은 생겼습니다.) interface는 어디 있나요? (- 그래서 typescript가 생겼습니다.) private, protected가 없으면 캡슐화는 어떻게 하죠? (- 그래서 지금 만들려고 하고 있죠.)

자바스크립트도 객체지향의 패러다임이 가장 핫한 시기에 만들어진 언어니 객체지향의 영향을 받지 않을 수 없었습니다. 하지만 자바스크립트를 맨 처음 설계한 사람은 객체 지향 프로그래밍에 회의적인 시각을 가지고 있었습니다. 그렇기에 당시 유행했던 객체지향의 개념은 그대로 가져오되 주류였던 Java와는 전혀 다른 방식으로 OOP를 풀어내게 됩니다.

Java와는 다르다! java와는...

https://stackoverflow.com/questions/245062/whats-the-difference-between-javascript-and-java
What's the difference between JavaScript and Java?
- Java and Javascript are similar like Car and Carpet are similar.

javascript 탄생비화

javascript를 창시한 Brendan Eich는 언어를 개발할 당시 유행하던 객체지향에 한계를 느끼고 LISP, scheme등 함수형 프로그래밍에 관심을 가지고 있었기에 함수형 프로그래밍의 형태로 언어를 만들고 싶어 했습니다. 하지만 Netscape의 그의 상사는 당시 개발자들이 제일 많이 쓰던 Java와 같은 문법으로 만들기 요구했기 때문에 결국 둘의 혼종의 형태로 세상에 나오게 되었습니다. :)

자바스크립트는 설계자의 철학도 그랬지만 웹 브라우저에서는 처음부터 복잡한 언어를 만들 생각이 없었습니다. 그저 아주 간단하고 작은 형태의 스크립트 언어면 될 거라고 생각을 했어요. 그래서 복잡한 class나 설계, 추상화나 다형성등을 만들고 싶지 않았습니다.

하지만 당시 가장 인기가 있었던 Java 개발자들을 끌어 들이고 싶었기 때문에 Java의 문법과 패러다임은 최대한 유지를 해보고자 노력을 하게 되었습니다.

그래서 class가 없는 함수형 언어를 기반으로 하지만 객체 지향 프로그래밍 맛을 느낄 수 있는 언어가 탄생하였습니다.

javascript 설계자에 빙의가 되어 봅시다!

'OOP는 너무 복잡해! 특히 class 문법은 너무 너무 복잡해. 최대한 간단하게 만들자... 그러면서도 객체 지향 처럼 상속, 추상화, 다형성을 할 수 만 있으면 되는거 아닐까?'

라는 목적을 가지고 javascript 최초 설계를 어떻게 했을지 한번 상상을 해보았습니다.

class가 없는데 객체는 어떻게 만들게 할까?
그냥 class없이 바로 Object를 생성할 수 있게 하면 되겠다. js는 no class 언어!
그러면 java처럼 o.name, o.age 이렇게 객체문법을 쓸 수 있겠지.

var cat = {
  name: "냥이",
  age: 1
}

console.log(o.name)
console.log(o.age)

타입이 없는데 객체가 가진 메소드는 어떻게 동작 해야 될까?
객체에 함수를 정의하면 java처럼 this를 넘겨주면 되지 않을까? Java문법과 유사하게~

var cat = {
  name: "냥이",
  age: 1
  speak: function() { console.log("야옹~~", this.name, this.age) }
}

cat.speak()

그러면 상속이랑 추상화, 다형성은 어떻게 할건데?
상속이란게 결국 이미 만들어진 값들을 받아서 쓰는 건데 class가 아니라 object에서 받아오면 되지 않을까? 그래서 있으면 내껄 쓰고 없으면 부모값을 쓴다면 결과는 똑같은 거 같은데.. 상속을 받는다는게 아니라 없으면 찾아간다는 식으로 연결을 해보는 건 어떨까?

var animal = {
  name: "",
  age: 0
  sleep: function() { console.log("sleep... zzz", this.name) }
  speak: function() { console.log("...", this.name) }
}

var cat = {
  name: "냥이",
  age: 1
  speak: function() { console.log("야옹~~~", this.name, this.age) }
}

// 상속을 받는(척 하지만 prototype 연결)
cat.__proto__ = animal

newObj.sleep() // cat에는 sleep없으니 연결된 animal를 찾아가서 sleep을 호출.. 이걸로 마치 상속 해결!
newObj.speak() // cat에 있는 속성이니 본인의 speak호출! 이걸로 다형성 해결!

그래도 class와 new를 이용해서 객체를 생성하는 방식은 필요하지 않을까?
그러면 ... 그래도 class는 안 넣을 거니까 function으로 하면 되겠네! 그러면 prototype을 통해서 이렇게 정리하면 class 없이도 객체지향을 일단 할 수는 있겠지?

function Cat(name, age) {
  this.name = name
  this.age = age
}

Cat.prototype.speak = function() { console.log("야옹~~~", this.name, this.age) }

var cat = new Cat("냥이", 1)
cat.speak()

이렇게 해서 class가 없이 object에 prototype체인은 연결하는 방식을 통해 객체지향의 문법과 객체지향의 상속, 추상화, 다형성을 해결하였습니다.

그리고 이러한 객체를 바탕으로 Object, String, Number, Function, Array등에도 객체지향의 개념을 부여한 기본 라이브러리를 만들어서 class는 없지만 마치 객체지향의 언어를 사용하는 느낌으로 코드를 작성할 수 있는 언어가 탄생하였습니다. 🎉

아니! 그래도 class가 없이 어떻게 객체지향을 하나요?

당시에는 class가 없이 프로그래밍을 한다는 것은 상상도 하기 힘들었습니다. 그렇기에 javascript 처음 출시가 되었을때에는 수준이 낮은 언어로 폄하당했습니다. 브라우저의 성능의 한계도 있었고 언어도 엄청 단순하게 구성이 되었는데 당연히(?) 있어야 할 class마저 없었으니 말이죠.

그래서 당시에는 수도없이 많은 class처럼 코딩을 하기 위한 라이브러리들이 엄청 많았고 그중에서 prototypejs가 큰 인기를 끌었습니다. 홈페이지를 보면 class를 쓰기 위한 API가 맨 처음 소개되는 것을 확인 할 수가 있습니다.

prototypejs
http://prototypejs.org/learn/

결국 제작자의 의도와 관계없이 가장 많이 요구하던 문법적인 기능이 바로 class였으며 결국(?) ES6에 와서는 class가 정식 문법이 되었습니다. 하지만 ES6의 class는 javascript 객체지향의 근간인 prototype 방식을 문법적으로 class처럼 보이게 만들어준 도구에 불과하지요.

타입도 없고 interface도 없네요?

오리처럼 걷고, 오리처럼 꽥꽥거리면 오리지...

// 타입이 뭣이 중한겨? 그냥 메소드를 가지고 있으면 실행하면 되지..
function test(duck) {
  if (typeof duck.walk === "function") {
    duck.walk()  
  }

  if (typeof duck.quack === "function") {
    duck.quack()  
  }
}

var duck1 = {...}
var duck2 = {...}

test(duck1)
test(duck2)

Duck Typing
https://ko.wikipedia.org/wiki/%EB%8D%95_%ED%83%80%EC%9D%B4%ED%95%91

자바스크립트는 타입으로 인한 복잡성을 추구하지 않기 위해서 객체의 타입을 따지지 않습니다. 같은 이름의 변수나 메소드를 가지고 있다면 같은 타입의 객체라고 사용 할 수 있습니다.

이를 통해서 복잡한 객체지향 프로그래밍의 설계를 복잡한 타입정의와 상속을 통해 interface를 맞추지 않고 아주 간단하게(?) 만들어 줄 수가 있습니다.

(물론 전통적으로 강력한 타입을 기반으로 하는 설계가 중요한 전통언어의 시각으로 보기에는 어딘가 조잡해 보였겠지요. 그래서 Typescript가 생겨났다는 것을 기억합시다.)

마! 우리는 함수가 1급 객체다!

논란이 많은 자바스크립트 였지만 그래도 초기 설계 중에 가장 칭찬을 받는 점! 바로 함수가 1급 객체라는 점입니다. javascript에서는 document.body.onclick = function(event) { ... } 와 같은 엄청 단순한 1줄짜리의 코드가 Java에서는 이러한 Listener 구현하기 위해서 상당히 복잡한 패턴을 사용해야 했습니다.

함수를 값으로 넘길 수 있고 익명함수와 클로저를 통해서 값을 보관하고 전달할 수 있다는 개념을 통해서 기존의 객체지향에서 복잡하게 구현을 해야했단 수많은 패턴들이 아주 아주 간단하게 해결이 되는 것을 확인했습니다.

이러한 함수가 값이 될 수 있다는 점은 추후 많은 객체지향 언어에 새로운 영감을 주었고 지금은 객체지향만 고집하지 않고 함수형과 객체지향을 적절히 섞어 쓰는 방식으로 발전을 하고 있습니다.

객체지향의 시각으로 보았을때 모자라던 언어가 이제는 객체지향의 패러다임에게 영향을 주고 있는 언어가 되었습니다.

그래서 javascript에서는 객체지향을 어떻게 해야 하나요?

언제나 그렇듯 코딩에는 정답은 없습니다. 그리고 계속 트렌드는 바뀔테니까요. 객체지향의 패러다임이 충만하던 시기에서 바라보던 javascript는 class도 없는 열화판 객체지향을 흉내낸 언어였습니다. 물론 이 사이 지극정성으로 ES를 돌봐주었기에 지금 사람같은 언어가 된 면도 있지만 결국 객체지향을 완벽히 지향하지 않았기에 지금의 자바스크립트가 생겨났고 이러한 개념이 다른 객체지향언어에 영향을 줄 만큼 강력하다는 것을 우리는 배웠습니다.

대부분의 객체지향 프로그래밍의 이론이나 설계등은 Java의 최전성기 시절에 구축되었던 방법론들입니다. 모든 것이 객체이며 객체간의 메시지의 흐름이 중요하고 객체를 중심으로 설계를 해보려고 하니 타입과 인터페이스들로 복잡한 프로그램이 만들어졌고 이러한 패턴들을 또 모아서 학습을 하곤 했습니다.

javascript는 java와는 태생이 다르기에 그 모든 것을 이해하더라도 결국은 javascript스럽게 하는 것이 가장 좋다입니다. 극단적으로 객체지향을 배제할 필요도 없고 복잡하게 객체지향으로 설계를 해야할 필요도 없습니다.

객체지향의 개념으로 데이터와 메소드를 통해 독립적인 작은 프로그램으로써 만들어 편리하게 재사용하는 장점을 취하되 객체들의 결합이 높아져서 프로그램이 과도하게 복잡해지지 않도록 굳이 객체로 만들지 않아도 될 부분들은 함수형으로 만들어서 결국 간단하고 가독성있고 덩치가 커져도 유지보수하기 좋으며 재사용이 편리한 코드를 만들어 내는 것이 javascript를 잘 하는 것이겠지요. javascript는 객체지향과 함수형의 2가지 관점을 모두 가지고 있는 언어기에 이러한 밸런스를 잡는 것이 중요합니다.

물론 언제나 말은 쉽습니다. 실천이 어려운 것이지요.

끝으로...

객체지향 프로그래밍은 프로그래밍 산업이 발전하면서 프로그램의 덩치가 커져가면서 생기는 어떤 문제점을 해결하기 위해서 나온 하나의 관점이나 방법론입니다. 기존의 방식으로는 변수들을 하나씩 관리하다 보니 변수명을 공유해서 써야 되는 문제들이 생기는 문제들이 발생했습니다. 연관있는 이름들을 하나로 묶어주는 구조체 라는 타입을 만드니 데이터를 중심으로 프로그래밍을 할 수 있게 되었습니다.

그렇다면 데이터와 함수를 한데 묶어서 관리를 하면 어떨까? 이렇게 하니 하나의 큰 프로그래밍을 작은 문제를 해결하는 독립적인 단위로 만들수가 있게 되었습니다. 이렇게 작은 단위로 관리를 할 수 있게 되니 개발과 유지보수가 간편하게 된다는 장점을 알게 되었습니다.

이러한 객체를 설계하고 찍어낼 수 있는 구조를 클래스라고 하고 클래스에 만들어진 인스턴스를 Object라고 하여 프로그래밍의 모든 것들을 이러한 객체로 간주하여 객체간의 상호작용을 중심으로 생각하고 설계하는 프로그래밍 개념이 바로 Object-Oriented Programming (OOP) = 객체지향 프로그래밍 입니다.

객체라는 개념을 기반으로 만들어진 코드의 일부분들을 보다 효율적으로 재사용을 위해서 캡슐화, 상속, 추상화, 다형성이라는 추가적인 개념이 추가되면서 객체지향은 발전해 왔습니다.

그러던 중 javascript라는 언어는 객체지향의 패러다임은 계승하면서도 단순하고 간단한 언어를 추구하다보니 Java의 문법을 쓰지만 함수형을 기반으로 하는 class-free 프로토타입 기반 OOP라는 독특한 변화구를 가진 객체지향맛 언어가 탄생했습니다.

javascript가 쏘아올린 작은 공으로 인해 객체지향 언어가 최고라는 패러다임은 적절히 함수형 프로그래밍과 섞으면 좋다 라는 것을 알게 되었고 Typescript가 대중화되면서 다시 Type과 Class, Interface가 있는 언어가 되었지만 이미 패러다임은 이미 바뀌었습니다.

javascript는 함수형 언어도 객체지향 언어도 아니지만 또 함수형 언어이기도 하고 객체지향 언어이기도 합니다. 많은 사람들의 손이 타다보니 여러 취향을 반영할 수 있는 재미난 언어가 되어버렸습니다.

객체지향의 패러다임은 대부분은 Java가 전성기이던 시절에 완성이 되었습니다. 물론 지금이야 멀티패러다임 언어가 흔해졌기에 모두가 그렇지는 않지만 대부분의 객체지향 패러다임은 온전히 javascript와는 맞는 옷은 아닙니다.

데이터와 메소드를 기반으로 작은 문제를 독립적으로 해결할 수 작은 프로그램을 만들어 코드의 재사용을 높이고 간결한 문법을 통해 개발과 유지보수를 편하게 할 수 있다는 장점은 취하고 객체를 연결하는 과정에서 과도하게 객체지향을 통해 만들어진 복잡한 구조를 만들지 않도록 지양하고 이를 함수형 프로그래밍이 가지고 있는 장점과 결합을 하여 적절한 선을 찾아내고 만들어 가는 것이 현대 javascript에서 객체지향 프로그래밍을 잘한다라고 볼 수 있는 것 같아요.

그러기 위해서는 역시 많은 경험을 통해서 무엇이 좋고 무엇이 나쁜지 알아가서 본인만의 눈을 기르는 것이 중요하겠지요.

이 글이 그래도 무작정 경험을 쌓기보다는 조금 더 전략적으로 접근을 할 수 있는 길잡이가 될 수 있었기를 바랍니다.

긴 글 읽어 주셔서 감사합니다 ❤️

profile
Svelte, rxjs, vite, AdorableCSS를 좋아하는 시니어 프론트엔드 개발자입니다. 궁금한 점이 있다면 아래 홈페이지 버튼을 클릭해서 언제든지 오픈채팅에 글 남겨주시면 즐겁게 답변드리고 있습니다.

19개의 댓글

comment-user-thumbnail
2022년 3월 3일

자바에서 객체지향을 하니까 자바스크립트에서도 객체지향을 해야 한다고 생각하기 보다는 변화에 유연하고 반복을 줄이며 가독성 좋은 설계를 위해 객체지향 설계를 사용한 것처럼, 자바스크립트를 이용해 어떻게 좋은 설계를 할 수 있을까 고민하는 것이 더 올바른 방향이 되겠네요. 좋은 글 작성해주셔서 감사합니다!

1개의 답글
comment-user-thumbnail
2022년 3월 8일

너무 재밌게 잘읽었네요 :) 항상 양질의 정보 감사합니다.

1개의 답글
comment-user-thumbnail
2022년 3월 10일

좋은 포스트 감사합니다.

아래는 오탈자 제보입니다.

Brendan Erich
Brendan Eich

... 함수형 언어를 기반으로 하는 지만 객체 지향...
... 함수형 언어를 기반으로 하지만 객체 지향...

1개의 답글
comment-user-thumbnail
2022년 3월 11일

타입스크립트도 함수형 프로그래밍이 가능한가요? 그럼 TS가 Vanilla JS처럼 Vanilla TS로 브라우저에서 돌아가며 DOM 조작도 가능하고 리액트 등의 프레임워크에서도 쓰일 수 있고 좀 더 완전한 객체지향과 함수형 프로그래밍이 모두 가능하다면 JS를 언젠가 완전히 대체하게 될까요?

1개의 답글
comment-user-thumbnail
2022년 4월 5일

우와 글 내용 너무 좋아서 집중해서 읽었네요 ㅎㅎㅎ 최근 객체지향에 대해 관심이 많았는데 이렇게 기술이 탄생한 배경부터 거슬러 설명하니까 객체지향이라는 개념을 바라보는 시각이 좀 더 달라지고 쉽게 이해됐어요!! 좋은 글 감사합니다!!
추가로 본문에 cat 메서드가 car라고 써진 오탈자가 있는 것 같아서 제보드립니다!!

// 상속을 받는(척 하지만 prototype 연결)
car.__proto__ = animal

// 상속을 받는(척 하지만 prototype 연결)
cat.__proto__ = animal
1개의 답글
comment-user-thumbnail
2022년 4월 6일

정말로 잘 읽었습니다. 유익한 자료 감사합니다. :)

1개의 답글
comment-user-thumbnail
2022년 4월 29일

좋은글 잘 읽었습니다.

1개의 답글
comment-user-thumbnail
2022년 5월 14일

❤ 누르려고 회원가입 했습니다!!!
너무 좋은 글 감사합니다!!!

1개의 답글
comment-user-thumbnail
2022년 6월 11일

퍼킹 어썸하네요

1개의 답글