WeniVooks

검색

JavaScript 베이스캠프

실제 만든 게임 코드로 보는 Class의 활용

1. class

ES6 부터 class라는 키워드를 사용할 수 있게 되었습니다.

클래스는 일종의 설계 도면 또는 템플릿입니다. 이 설계 도면을 통해 생산된 생산품을 인스턴스라 합니다. 클래스, 인스턴스는 중요한 용어이니 이 2개의 용어를 기억하며 글을 읽어주세요.

실제 개발된 예제를 보며 설명을 하겠습니다. 아래 게임을 잠시 해보고 오세요. 위니브에서 교육용으로 개발한 게임입니다. 해당 게임은 javascript로 개발되었습니다.

Phaser를 활용한 게임

이 게임에는 주인공, 몹, 빌런이 있습니다. 이 주인공, 몹, 빌런에게는 모두 같은 데이터가 필요합니다. 체력과 마력, 공격력, 방어력같은 데이터(구성 요소) 입니다. 또 같은 기능(구성 요소) 도 필요합니다. 공격을 하는 기능, 움직이는 기능, 아이템을 드랍하는 기능 등이요. 또 몹 같은 경우에는 수백마리가 필요할 수 있습니다. 완전히 동일한 코드가 반복(클래스를 만드는 조건) 되어야 합니다. 빌런처럼 꼭 반복이 많이 안되더라도 구조화(클래스를 만드는 조건) 가 필요한 경우에도 클래스를 사용합니다.

이러한 공통의 데이터와 기능을 한데 묶어서 클래스로 만들어 놓으면 편리합니다. 예를 들어 몹이 100마리가 있다해서 100개의 코드를 작성할 필요가 없습니다. 설계 도면(클래스)이 있다면 이것으로 100개의 몹(인스턴스)를 만들 수 있습니다.

아래는 실제 이 게임을 만들 때 사용한 코드를 간소화 한 것입니다.

class Mob {
  constructor(scene, x, y, texture, animKey, initHp, dropRate) {
    this.scene = scene; // 몹이 등장하는 장면
    this.x = x; // 몹의 x좌표
    this.y = y; // 몹의 y좌표
    this.texture = texture; // 몹의 이미지
    this.animKey = animKey; // 몹의 애니메이션
    this.initHp = initHp; // 몹의 초기 체력
    this.dropRate = dropRate; // 몹이 아이템을 드랍할 확률
 
    this.m_speed = 50; // 몹의 속도
    this.m_hp = initHp; // 몹의 체력
    this.m_dropRate = dropRate; // 몹이 아이템을 드랍할 확률
    this.m_isDead = false; // 몹이 죽었는지 여부
  }
 
  //몹에 위치 변경
  update() {
    // 주인공 쪽으로 몰려가는 기능 구현
    // 생략
  }
 
  // mob이 dynamic attack에 맞을 경우 실행되는 함수입니다.
  hitByDynamic(weaponDynamic, damage) {
    // 생략
  }
 
  // mob이 static attack에 맞을 경우 실행되는 함수입니다.
  hitByStatic(damage) {
    // 생략
  }
 
  // 몹이 죽었을 때 실행되는 함수입니다.
  die() {
    // 죽었을 때 아이템을 내려놓고 영웅에게 경험치를 주어야 합니다.
    // 생략
  }
}
class Mob {
  constructor(scene, x, y, texture, animKey, initHp, dropRate) {
    this.scene = scene; // 몹이 등장하는 장면
    this.x = x; // 몹의 x좌표
    this.y = y; // 몹의 y좌표
    this.texture = texture; // 몹의 이미지
    this.animKey = animKey; // 몹의 애니메이션
    this.initHp = initHp; // 몹의 초기 체력
    this.dropRate = dropRate; // 몹이 아이템을 드랍할 확률
 
    this.m_speed = 50; // 몹의 속도
    this.m_hp = initHp; // 몹의 체력
    this.m_dropRate = dropRate; // 몹이 아이템을 드랍할 확률
    this.m_isDead = false; // 몹이 죽었는지 여부
  }
 
  //몹에 위치 변경
  update() {
    // 주인공 쪽으로 몰려가는 기능 구현
    // 생략
  }
 
  // mob이 dynamic attack에 맞을 경우 실행되는 함수입니다.
  hitByDynamic(weaponDynamic, damage) {
    // 생략
  }
 
  // mob이 static attack에 맞을 경우 실행되는 함수입니다.
  hitByStatic(damage) {
    // 생략
  }
 
  // 몹이 죽었을 때 실행되는 함수입니다.
  die() {
    // 죽었을 때 아이템을 내려놓고 영웅에게 경험치를 주어야 합니다.
    // 생략
  }
}

코드는 아래 링크에서 확인할 수 있습니다.

게임 코드 보러가기

2. class의 사용법

class는 키워드이름, 중괄호로 이뤄져 있습니다. 중괄호 안에는 생성자(constructor), 메서드(method)가 들어갑니다. 클래스의 결과물은 인스턴스를 생성하는것입니다.

// ES6부터 도입된 class를 사용한 방식 class Robot { constructor(name) { // 생성자 함수라고 합니다. 하나의 클래스는 하나의 생성자만 정의할 수 있습니다. // 생성자 함수는 new 키워드가 호출될 때 자동으로 실행됩니다. this.name = name; } sayYourName() { // 메소드를 정의합니다. 메소드는 클래스가 생성한 인스턴스를 통해 사용할 수 있습니다. console.log(`삐리비리. 제 이름은 ${this.name}입니다. 주인님.`); } } // 클래스를 통해 인스턴스를 생성합니다. const bot = new Robot('mura'); bot.sayYourName(); // 삐리비리. 제 이름은 mura입니다. 주인님.

이전에는 함수를 이용해 객체를 생성하였습니다. 아래는 이전에 함수로 객체를 생성하던 방식입니다. 이 방식은 프로토타입 기반의 객체 생성 방식입니다.

// 이전에 함수로 객체를 생성하던 방식 function Robot(name) { this.name = name; } Robot.prototype.sayYourName = function () { console.log(`삐리비리. 제 이름은 ${this.name}입니다. 주인님.`); } const bot = new Robot('mura'); bot.sayYourName(); // 삐리비리. 제 이름은 mura입니다. 주인님.

자바스크립트만의 사용자 정의 타입 생성(객체 생성) 방법을 다른 언어의 클래스 문법처럼 바꿔준 것이 바로 자바스크립트 클래스(class)입니다.

클래스는 단순히 Syntactic sugar(설탕 문법)인가요?

이처럼 내부적인 동작은 동일하지만 더 보기 좋고 편리하게 개선된 문법을 슈가신텍스 (Syntactic sugar) 라고 부릅니다. 다만 class 문법은 단순한 설탕문법은 아닙니다. 여러가지 추가된 문법이 있습니다. 예를 들어 정적 메서드, 비공개 프로퍼티 등이 있습니다. 이러한 추가된 문법을 통해 class는 기존의 프로토타입 기반의 객체 생성 방식보다 더 많은 기능을 제공합니다. 그래서 class는 단순한 설탕 문법이 아닌 새로운 객체 생성 방식으로 볼 수 있습니다.

베이스캠프 강의는 클래스를 이 이상 다루지 않으니 더 관심이 있으신 분들은 추가로 학습을 하시길 바랍니다.

3. class 상속받기

클래스의 상속은 extends 키워드를 사용합니다. 상속을 받는 클래스는 파생 클래스(derived classes)라고 부릅니다.

class Animal { constructor(name) { this.name = name; } sayHello() { console.log(`Hello, I'm ${this.name}.`); } } class Dog extends Animal { constructor(name, breed) { super(name); this.breed = breed; } bark() { console.log('Woof!'); } } const dog = new Dog('Buddy', 'Labrador'); dog.sayHello(); // Hello, I'm Buddy. dog.bark(); // Woof!

위 예제에서 Dog 클래스는 Animal 클래스를 상속받습니다. Dog 클래스의 constructor에서 super()를 호출하여 부모 클래스의 constructor를 실행하고, 자식 클래스에서 필요한 추가 속성(breed)을 정의합니다. Dog 클래스는 부모 클래스(Animal)의 sayHello() 메서드를 그대로 사용할 수 있으며, 자신만의 메서드(bark)도 추가로 가지고 있습니다.

이처럼 상속을 사용하면 기존 클래스의 기능을 재사용하면서도 필요에 따라 자식 클래스에서 기능을 추가하거나 변경할 수 있어 코드의 재사용성과 유지보수성이 높아집니다.

4. 비공개(private) 프로퍼티

비공개 프로퍼티는 객체 안의 중요한 정보를 안전하게 보호하여 프로그램이 뜻하지 않게 변경되는 것을 막는 역할을 합니다. 만약 여러분이 class를 통해 인스턴스를 만들었을 때 보통 우리는 아무런 제약없이 인스턴스의 프로퍼티에 접근하는 것이 가능합니다.

class Robot { constructor(name) { this.name = name; } sayYourName() { console.log(`삐리비리. 제 이름은 ${this.name}입니다. 주인님.`); } } const bot = new Robot('mura', 1234); console.log(bot.name); // mura

또한 프로퍼티의 값을 수정하는 것도 가능합니다. 만약 접근하려는 프로퍼티가 없다면 새로운 프로퍼티가 생성됩니다.

bot.name = 'jay';
bot.name; // jay
bot.name = 'jay';
bot.name; // jay

하지만 중요한 데이터를 조심스럽게 다뤄야 할 경우, 그래서 이런 데이터를 외부에서 함부로 수정할 수 없게 만들고 싶을 때 우리는 비공개 프로퍼티로 데이터를 변경할 수 있습니다. 이렇게 하면 외부에서 함부로 접근하거나 수정할 수 없게 됩니다.

class Robot { #password; constructor(name, pw) { this.name = name; this.#password = pw; } sayYourName() { console.log(`삐리비리. 제 이름은 ${this.name}입니다. 주인님.`); } getPassword() { return this.#password; } setPassword(pw) { this.#password = pw; } } const bot = new Robot('mura', 1234); bot.name = 'licat'; bot.password = 5678; console.log(bot.name); console.log(bot.password); // 값이 변경된 것이 아닙니다! console.log(bot.getPassword()); // 1234, 값이 그대로입니다! // bot.#password = 9999; // SyntaxError!

# 키워드를 이용하면 프로퍼티를 비공개로 전환할 수 있습니다. 이제 #password 의 값에 접근하고 수정하려면 반드시 해당하는 기능을 하는 메서드를 사용해야합니다. 이때 값을 불러오는 메서드를 getter 메서드, 값을 수정하는 메서드를 setter 메서드로 부릅니다. get, set 키워드를 이용해 아래처럼 코드를 좀 더 단순화 할 수 있습니다.

class Robot { #password; constructor(name, pw) { this.name = name; this.#password = pw; } sayYourName() { console.log(`삐리비리. 제 이름은 ${this.name}입니다. 주인님.`); } get password() { console.log('get password 호출') return this.#password; } set password(pw) { console.log('set password 호출') this.#password = pw; } } // get과 set사용 const bot = new Robot('재현', 1234); console.log(bot.password); // 1234 bot.password = 5678; console.log(bot.password); // 5678

이렇게 get과 set 키워드를 사용하면 마치 객체의 프로퍼티에 접근하듯 값을 다룰수 있게 됩니다. 여기서 password는 비공개 프로퍼티인데 bot.password로 접근하는 것처럼 보입니다. 하지만 실제로는 getter, setter 메서드를 호출하는 것입니다. 이는 혼란을 줄 수 있으니 주의해야합니다.

이번에는 혼란을 주지 않는 다른 예제를 살펴보도록 하겠습니다.

class Robot { #password; constructor(name, pw) { this.name = name; this.#password = pw; } get userInfo() { return this.name + " " + this.#password; } set userInfo(info) { const inputData = info.split(" "); this.name = inputData[0]; this.#password = inputData[1]; } } const bot = new Robot('mura', 1234); console.log(bot.userInfo); // mura 1234 bot.userInfo = 'licat 5678'; console.log(bot.userInfo); // licat 5678

이렇듯 get과 set은 마치 객체의 프로퍼티를 호출, 수정하는것 처럼 사용할 수 있습니다.

get 과 set을 사용할 때 주의할 점!

get과 set을 사용하면 마치 객체의 프로퍼티를 수정하는것 같은 간편함을 느낄 수 있습니다. 하지만 해당 코드를 직접 작성하지 않은 협업자들에게는 오해를 불러일으킬 소지가 있습니다. get, set 안에 어떤 로직이 들어있는지 파악하지 못하고 단순히 객체의 프로퍼티를 수정한다는 착각을 일으킬 수 있기 때문입니다.

때문에 협업 시에는 주석이나, 가이드 문서를 만들어 충분한 정보를 제공해주는것이 좋습니다.

또한 private 필드 선언을 한 프로퍼티의 경우 get, set 키워드를 사용하면, 비공개 하였음에도 불구하고 일반 프로퍼티처럼 접근이 가능하게됩니다.

언더바(_)를 사용한 비공개 프로퍼티

#(샾)이 나오기 전에는 _(언더바)를 사용하는 경우도 있었고, 지금도 이렇게 사용하고 있는 곳이 있습니다.

언더바는 읽기 전용으로 사용하겠다는 표시입니다. 다만 강제사항이 아니라서 실제로 수정이 가능한 변수입니다. 사용하는 곳이 있기도 하니 기억해주세요.

아직 비공개 프로퍼티가 널리 퍼지진 않았습니다. 나온지 얼마 안된 문법이라 주의를 요합니다.

  • 개발자 도구에서는 #을 했어도 접근이 가능합니다. .html파일에서 실행해주세요.
9장 설계 도면 Class10장 비동기 프로그래밍