목차7개 항목
출처와 참고
테오(teo)의 「객체지향 프로그래밍과 javascript (약간의 역사를 곁들인...)」(2022.03)를 바탕으로 재구성하고, 캡슐화 수단은 javascript.info(모던 JavaScript 튜토리얼)의 「private, protected 프로퍼티와 메서드」 글로 보강한 노트다.
기술은 문제를 풀려고 나온다
기술 발전은 대개 네 시기를 돈다. 문제 인식(다들 겪지만 해법은 제각각) → 이름 붙은 기술 등장(해법이 먼저 있고 이름은 나중에 붙는다) → 부흥(모든 문제를 새 관점으로 풀려 한다) → 정체(새 한계가 보이고 다시 문제 인식으로). 낯선 용어는 대개 세 번째 시기에 만들어지므로, 용어보다 어떤 문제를 풀려고 나왔는지를 먼저 보면 이해가 쉽다.
객체 지향까지의 흐름
실제 역사는 여러 갈래로 겹쳐 진행됐지만, 개념의 인과를 보기 위해 한 줄로 단순화하면 다음과 같다.
flowchart LR g["goto<br/>흐름 제어가 엉킨다"] --> p["절차적(프로시저)<br/>함수로 재사용"] p --> n["네임스페이스<br/>전역 이름 충돌을 prefix로 해결"] n --> s["구조체<br/>연관 데이터를 하나로 묶는다"] s --> c["클래스<br/>데이터 + 그 데이터를 다루는 함수를 함께"] c --> o["객체 지향"]
| 단계 | 푼 문제 | 남은 문제 |
|---|---|---|
| goto | 같은 코드를 다시 실행 | 실행 순서를 강제로 바꾸니 흐름을 따라갈 수 없다 |
| 절차적 프로그래밍 | 호출하고 돌아오는 프로시저(함수)로 재사용 | 전역 변수 이름이 겹친다 |
| 네임스페이스 | 파일·모듈 단위로 이름 접두어를 관리 | character1_hp, character1_mp처럼 연관 데이터가 흩어진다 |
| 구조체 (C) | 연관 데이터를 하나로 묶어 데이터 중심으로 코딩 | 특정 구조체만 받는 함수군(character_attack(c))이 생긴다 |
| 클래스 | 구조체 + 그 구조체를 받는 함수 = 클래스 | - |
데이터와 처리 방식이 한 단위로 묶이자, 작은 프로그램이 독립적으로 도는 것처럼 보였다. 큰 프로그램을 통째로 짜는 대신 작은 부품을 만들어 조립하는(bottom-up) 방식이 가능해졌다. 클래스는 설계도, 그로 찍어 낸 것은 값과 동작을 함께 가진 사물 같아서 객체(object)라고 불렀다. 프로그램을 객체와 객체 사이의 메시지로 보는 관점이 객체 지향 프로그래밍(OOP)이다. 메시징을 본질로 보는 원래의 관점은 객체 지향의 본질: 메시징에 정리했다.
재사용을 위한 네 가지 개념
부품을 많이 만들어 두고 재사용하는 것이 핵심이 되자 개념들이 붙었다.
| 개념 | 푸는 문제 | 예 |
|---|---|---|
| 캡슐화 | 밖에서 알 필요 없는 내부를 숨겨 안정성과 사용성을 높인다 | #mp는 클래스 안에서만 바꾼다 |
| 상속 | 객체의 일부분만 재사용한다 (DRY) | 저글링과 히드라가 같은 이동·사망 로직을 공유 |
| 추상화 | 공통 부분에 상위 개념의 이름을 붙인다 | 사과·바나나 → 과일, 저글링·히드라 → Unit |
| 다형성 | 같은 타입으로 다루되 각자 방식대로 동작한다 | units.forEach(u => u.moveTo(x, y))가 지상·공중 유닛마다 다르게 움직인다 |
C++과 Java로 OOP가 주류가 됐다. 하지만 클래스만 쓴다고 좋아지지 않았다. 객체 사이 관계를 잘 정해야 하고 초기 설계 비용이 컸다. 그래서 SOLID 같은 설계 원칙과, 반복되는 설계 문제의 해법에 이름을 붙인 디자인 패턴(GoF 20여 개)이 뒤따랐다.
JavaScript의 객체 지향
JavaScript를 만든 Brendan Eich는 OOP에 회의적이었고 Scheme 같은 함수형 언어에 관심이 있었다. 회사는 당시 가장 인기 있던 Java처럼 보이길 원했다. 결과는 Java의 문법을 입은, 클래스 없는 함수형 기반 언어다. 브라우저의 작은 스크립트 언어로 출발했으니 복잡한 클래스 체계도 원하지 않았다.
| 객체 지향의 요소 | 전통적 OOP (Java) | JavaScript의 해법 |
|---|---|---|
| 객체 생성 | 클래스로 인스턴스를 찍는다 | 클래스 없이 객체 리터럴 {}로 바로 만든다 |
| 메서드 | 클래스에 정의 | 객체에 함수를 넣고 this로 자기 자신을 넘긴다 |
| 상속 | 클래스 상속 | 프로토타입 연결: 내게 없으면 연결된 객체를 찾아간다 |
| 다형성 | 오버라이드 | 내게 있으면 내 것을 쓴다 |
new | 클래스 생성자 | 생성자 함수 + prototype |
| 타입·인터페이스 | 명시적 타입 | 덕 타이핑: 같은 이름의 메서드가 있으면 같은 타입으로 쓴다 |
| 이벤트 리스너 | 리스너 인터페이스 구현 패턴 | 일급 함수: el.onclick = function () {} 한 줄 |
const animal = { sleep() { console.log('zzz', this.name); } };
const cat = Object.create(animal); // 프로토타입 연결
cat.name = '냥이';
cat.sleep(); // cat에 없으니 animal에서 찾는다클래스가 없다는 이유로 처음엔 낮은 수준의 언어로 취급받았고, prototype.js처럼 클래스를 흉내 내는 라이브러리가 유행했다. 결국 ES6에 class가 들어왔지만 프로토타입 위에 얹은 문법일 뿐이다(class 문법). 체인을 따라 찾는 원리와 new가 하는 일은 프로토타입과 상속에 있다. 반대로 함수를 값으로 다루는 설계는 많은 객체 지향 언어에 영향을 줘, 지금은 함수형과 객체 지향을 섞어 쓰는 쪽으로 흐름이 바뀌었다.
캡슐화: 내부 인터페이스와 외부 인터페이스
캡슐화는 객체의 멤버를 외부 인터페이스(바깥에서 써도 되는 속성·메서드)와 내부 인터페이스(같은 클래스 안에서만 쓰는 구현 세부)로 나누는 것이다. 커피 머신은 버튼 몇 개만 드러내고 보일러와 배관은 커버 안에 숨긴다. 쓰는 사람은 버튼만 알면 되고, 커버를 벗기면 오히려 다치기 쉽다.
| 이점 | 내용 |
|---|---|
| 오용 방지 | 바깥 코드가 내부 상태를 직접 바꿔 객체를 망가뜨리지 못한다 |
| 바꾸기 쉬움 | 바깥이 내부에 의존하지 않으므로, 외부 인터페이스만 지키면 내부 메서드의 이름·인자를 자유롭게 고칠 수 있다. 사용자는 새 버전으로 쉽게 올라간다 |
| 복잡성 은닉 | 드러난 부분이 작을수록 쓰기 쉽고 문서화하기 쉽다 |
JavaScript에서 내부를 숨기는 수단은 네 가지다.
| 수단 | 강제력 | 자식 클래스 | 비고 |
|---|---|---|---|
_name 관례 | 없음(약속) | 접근 가능 | 다른 언어의 protected 역할 |
#name private | 언어가 막음 | 접근 불가 | 클래스 본문 안에서만(class 문법) |
| 클로저 | 언어가 막음 | 해당 없음 | 팩토리 함수의 지역 변수(스코프와 클로저) |
| 모듈 스코프 | 언어가 막음 | 해당 없음 | export하지 않은 것(모듈) |
상속 계층이 내부를 공유해야 하면 _, 그 클래스만 알아야 하면 #을 쓴다.
그래서 JavaScript에서는
대부분의 OOP 이론과 패턴은 Java 전성기에 만들어졌다. 모든 것을 객체로 보고 타입과 인터페이스로 관계를 설계하는 방식이 JavaScript에 꼭 맞는 옷은 아니다.
- 데이터와 메서드를 묶어 독립된 작은 부품으로 재사용하는 장점은 취한다.
- 객체 사이 결합이 높아져 구조가 과하게 복잡해지는 것은 피한다. 굳이 객체일 필요 없는 부분은 함수형으로 쓴다.
- 극단적으로 객체 지향을 배제할 필요도, 무겁게 설계할 필요도 없다. 두 관점 사이의 균형이 JavaScript다운 코드다.
정리
- 객체 지향은 코드가 커지며 생긴 이름 충돌과 흩어진 데이터 문제를 "데이터 + 그 데이터를 다루는 함수"로 묶어 푼 관점이다.
- 캡슐화·상속·추상화·다형성은 모두 부품 재사용을 위한 장치다.
- 객체 지향도 은탄환이 아니어서 SOLID와 디자인 패턴이 뒤따랐다.
- JavaScript는 클래스 대신 프로토타입, 타입 대신 덕 타이핑, 리스너 패턴 대신 일급 함수로 OOP를 풀었다.
class는 그 위의 문법이다. - JavaScript에서는 객체 지향과 함수형 중 하나를 고르기보다 간결하고 바꾸기 쉬운 쪽으로 섞는다.
관련 노트: 객체 지향의 본질: 메시징, class 문법, 프로토타입과 상속, JavaScript 디자인 패턴: 생성 패턴, 프론트엔드와 SOLID 원칙, 함수형 프로그래밍, 소프트웨어 설계와 공학