클래스란 무엇인가
이 글에는 이해를 돕기 위한 거짓말이 조금 포함되어 있습니다.
코드로 컴퓨터에게 일 시키기
여러분은 햄버거집을 운영하는 사장입니다.
지금껏 여러분이 코딩이라고, 컴퓨터에게 어떤 일을 시키던 것들은 버거집에서 알바생이 지켜야할 규칙들을 상세하게 적어내려가는 일이었고, 그 일을 처리하는 알바생은 컴퓨터인데 이녀석은 매우 멍청하지만 매우 정직합니다. 다르게 말하자면 상식은 전혀 통하지 않지만 하라는 일은 정확하게 해냅니다.
이것이 오늘 코딩을 비유하는 방식입니다.
효율적인 햄버거 레시피 관리
클래스(class)는 ‘어떻게 하면 모든 햄버거 레시피를 쉽게 관리할 수 있을까?’라는 생각에서 출발한 개념으로
- 레시피를 보관할 때는 공간을 적게 차지하고
- 공통된 부분이 있는 것들을 관리할 때 용이하며
- 그러면서도 각 메뉴에 차별점을 두기 쉬운
특징을 가지고 있습니다.
알바생은 이 부분을 전혀 신경쓰지 않습니다. 이 알바생은 레시피가 100장이든 1000장이든 하라는 일은 정확하게 할 줄 아니까요. 아쉽게도 상식이 없어서 우리가 레시피를 잘못 관리하면, 예를 들어 버거에 된장을 바르라고 레시피를 남겨놓으면, 주저도 의심도 없이 그대로 일을 저질러버릴 겁니다. 그건 전적으로 우리 책임이 됩니다.
우리 입장은 좀 다릅니다. 레시피가 100장, 1000장 늘어나면 뭔가 수정하고 싶을 때 100장, 1000장 다 읽어 검토하고, 변경해야하는 엄청난 수고를 해야합니다. 만약, 자연재해로 상추 가격이 높아져서 모든 버거에 들어가는 상추조각을 조금 줄여야한다고 생각해봅시다….
정말로 1000장을 다 보면서 상추 수를 변경할 생각이신가요? 재해가 끝나면 다시 상추조각수를 원상복구 시켜야할텐데요 😢
우리는 좀 더 효율적으로 레시피를 관리할 필요가 있습니다.
공통 구성 찾기
햄버거는 위, 아래가 빵입니다.
적어도 우리 매장에서 팔고 있는 버거들은 모두 그런 모습입니다.
그러니까, 우리 매장 레시피는 공통적으로 바닥 빵을 두고 시작합니다.
1
2
3
4
5
6
7
8
9
class Burger {
constructor() {
// 버거를 제작할 때 일단 바닥 빵을 둡니다.
}
end_of_burger() {
// 버거를 마무리할 때 맨 위에 빵으로 덮습니다.
}
}
여기서 constructor()
는 클래스가 생성될 때 시작하는 함수로 우리가 앞서 사용했던 setup()
과 동일한 방식으로 동작합니다(생성될 때 단 한번 실행). constructor()
는 클래스의 기본 구성요소로, 반드시 존재하고, 시작할 때 자동으로 동작합니다.
아래 있는 end_of_burger()
는 클래스 안에 있는 함수입니다. 호출해야 동작하는, 우리가 사용하던 그 함수에요.
클래스는 함수도 변수도 구성/관리할 수 있습니다. 클래스라는건 뭔가 우리가 작성중인 스크립트의 축소판 같네요.
우리는 다음 코드를 입력하여 우리의 새 버거를 만들어낼 수 있습니다.
1
var NewBurger = new Burger(); // 새로운 버거 만들기
아직 빵 밖에 없지만 말이죠 !
빵은 같아도 내용물은 달라야해
모든 메뉴에는 빵이 들어가지만 모든 메뉴에 치즈가 들어가진 않습니다.
우리는 보통 메뉴 이름을 통해서 내용물을 구분하니까 그렇게 동작하도록 해야겠어요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Burger {
constructor(name: String) {
// 버거를 제작할 때 일단 바닥 빵을 둡니다.
switch(name) {
case 'cheese':
// 치즈버거는 치즈를 올릴 겁니다.
break;
}
this.end_of_burger();
}
end_of_burger() {
// 버거를 마무리할 때 맨 위에 빵으로 덮습니다.
}
}
...
var Burger = new Burger('cheese');
constructor()
도 함수와 동일하게 동작하기 때문에 인자 구성을 할 수 있습니다.
그리고는 this
라는걸 쓰게 되는데, 여기서 this
는 Burger
클래스를 말합니다. 영어 해석 그대로 이것(이 클래스)
인 셈이죠.
.
은 ~안에
라는 뜻입니다.
this.end_of_burger()
는 그러니까, 이 클래스에 안에 있는 end_of_burger() 함수를 쓰겠다는 뜻이 됩니다.
더 다양한, 더 짧은 구성을 위해
그런데 저런식으로 Burger 클래스를 구성하면 switch
구문이 엄청 길어지겠죠?
그러면 우리가 100장, 1000장의 레시피를 쓰는 것과 다를게 없어지잖아요..
그래서 클래스에는 확장이라는 개념이 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Burger {
constructor(name: String) {
// 버거를 제작할 때 일단 바닥 빵을 둡니다.
}
end_of_burger() {
// 버거를 마무리할 때 맨 위에 빵으로 덮습니다.
}
}
class CheeseBurger extends Burger {
constructor() {
super(); // 여기서 바닥 빵을 둡니다.
// 치즈버거에는 치즈가 들어갑니다.
this.end_of_burger(); // 빵 덮기
}
}
...
var NewBurger = new CheeseBurger();
새로운 클래스인 CheeseBurger
는 Burger
클래스를 상속받아 만들었습니다.(extends는 확장이란 뜻이니 직역하자면 Burger의 개념을 확장하여 정도가 됩니다)
갑자기 생긴 super()
는 상속받았던 클래스의 해당 함수, Burger 클래스의 constructor()
를 실행시키겠다는 뜻입니다. 클래스에 내장되어 있는 기능입니다.
CheeseBurger
클래스 안에 end_of_burger()
함수가 없는데도 this
로 호출할 수 있는건 상속받았던 Burger 클래스에 있는 것을 그대로 사용할 수 있기 때문입니다.
Burger
로서 기본적으로 해야할 일들(시작하자마자 빵 두기, 마지막에 빵 덮기)이 이미 준비된 상태로 치즈버거일 때에만 해야할 일(치즈 넣기)만 신경써주면 치즈버거는 온전하게 만들어집니다.
더 더 다양하게, 더 더 복잡하게
메뉴 관리에 능숙해져, 몇장 안되는 레시피로 더 많은 메뉴를 관리하다보면 구조상 비슷하게 동작하긴하나 기본 개념을 타파하는 방식의 레시피를 준비하고 싶어질지 모릅니다.
예를 들면 민트초코파인애플버거
를 만들고 싶을지도 모르겠네요.
위 아래가 빵이 아닌 파인애플조각인 녀석으로 말이죠. 안돼
그 때부터는 다형성(Polymorphism)
이라는 개념이 들어가는데 지금 다루지는 않을 예정입니다. 햄버거 예시로 지나가듯 쉽게 설명하자면 상속받은 클래스에서 일부 함수를 덮어씌워 재가공하는 방법(이 경우, end_of_burger() 에서 빵 대신 파인애플로 덮기) 같은걸 할 수 있습니다.
햄버거로는 설명하기 어려운 개념들이 있으니 다음에 다른 예시로 준비해볼께요.