본문 바로가기
Learn to Code

[JS] 클래스(Class), 인스턴스(Instance), 정적 메소드(Static method), 그리고 서브클래스(Subclassing)

by CEOSEO 2021. 4. 9.
728x90
반응형

클래스(Class)와 인스턴스(Instance)

객체 지향 프로그래밍 패러다임을 따른다는 것은 하나의 모델이 되는 청사진(blueprint)를 만들고, 그리고 그 청사진을 바탕으로한 객체(object)를 만든다는 것을 의미한다. 하나의 모델이 되는 청사진, 바로 이것이 클래스(class)가 되는 것이고, 그 청사진을 따라 만들어진 것이 객체이면서 그 클래스의 인스턴스(instance)가 된다. 

 

 

<이전글: 객체 지향 프로그래밍>

2021.04.07 - [Learn to Code] - [JS] 객체 지향 프로그래밍(Object-Oriented Programming)

 

 

클래스(class)는 말하자면 ''이라고 생각할 수 있다. 무언가를 만들 때, 그 만들 물건의 겉 모양을 미리 만들어놓고 그대로 찍어낼 수 있게 하는 '틀' 말이다. 학창시절 한국사 시간의 한 내용을 떠올려보면 좀 더 쉽게 이해가 가능하다.

 

청동기시대 거푸집 (출처: 한국민족문화대백과사전)
거푸집을 사용하는 모습 (출처: 두피디아)

 

 

위 사진은 청동기 시대 유물인 거푸집의 모습과 그 거푸집을 사용하여 물건을 만드는 모습을 나타낸 것이다. 저때의 행동을 코딩이라 생각하고 대입시켜보면 아래와 같을 것이다.

 

 

 

 

하나의 클래스를 가지고 여러개의 인스턴스를 만들 수 있다. 각각 만들어진 인스턴스들은 개별적인 요소(예: 색깔)를 갖지만, 공통된 클래스를 가졌다는 점에서 여러가지들(예: 칼의 모양, 날카로움 등)을 공유할 수 있다. 

 

 

 

 

 

JS에서 클래스를 만드는 두가지 방법

자바스크립트에서 클래스를 만드는 방법은 크게 두가지가 있다. 첫번째는 ES6가 도입되기 이전에 사용하던 함수를 사용하는 방식이고, 두번째는 ES6 이후에 생긴 클래스 생성자를 이용한 방식이다. 두 방식 모두에서, 클래스를 만들땐 통상적으로 맨 앞글자를 대문자로 만든다 (예: Sword). 클래스의 메소드를 만들 때, ES6 방식같은 경우 생성자 함수 안에 class 키워드로 함께 묶어서 작성한다. 

 

 

// 방법1: 함수를 사용한 방식
function Sword(name, color, damage) {
  this.name = name;
  this.color = color;
  this.damage = damage;
}

// 방법1의 메소드
function.prototype.attack = function() {
  // 공격
}

function.prototype.defend = function() {
  // 방어
}


// 방법2: 클래스 생성자를 사용한 방식
class Sword {
  constructor(name, color, damage) {
    this.name = name;
    this.color = color;
    this.damage = damage;
  }
  
  // 방법2의 메소드
  attack() {
    //공격
  }
  
  defend() {
    // 방어
  }
}

 

 

 

인스턴스 만들기

만들어진 인스턴스는 Sword 클래스의 속성과 메소드를 갖게 된다. 아래와 같이 코드를 작성한다면 아주 간단하게 강력한 장미칼을 만들 수 있다.

 

 

// Sword 클래스의 인스턴스, 강력한 장미칼 만들기

let roseKnife = new Sword('Rose Knife', 'Rose Pattern', 999999999999)


// Sword 클래스의 메소드를 우리의 강력크한 장미칼이 사용할 수 있다

roseKnife.attack() // 장미칼로 강력하게 공격하기
roseKnife.defend() // 장미칼로 강력하게 방어하기

 

강력하다

 

 

 

또 다른 예시: 클래스, 인스턴스, 인스턴스 메소드 및 static 키워드

아래 예시는 호그와트 학생들을 모티브로 만든 클래스 예시이다.

 

우선 가장 먼저 Student 클래스를 만들어보자. 위 다른 예시와 마찬가지로, 새로운 클래스를 만들 땐 class 키워드를 사용하며, 그 안에서 생성자 (constructor) 함수가 불려왔다. 생성자에서 this를 불렀기 때문에, 여기서 this가 가리키는 대상은 new 키워드를 통해 향후 만들어질 Student 클래스의 인스턴스이다.

class Student {
  constructor(firstName, lastName, house) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.house = house
  }
}

 

 

 

클래스를 만드는 것만으론 아무일도 일어나지 않는다. 그 클래스의 인스턴스를 만들어야 실제 그 클래스를 기반으로한 객체가 만들어진다. new 키워드를 사용해서 위에 만든 Student 클래스의 인스턴스를 만들어보자. 이 과정은 'instantiate'한다고 불린다.

let harryPotter = new Student("Harry", "Potter", "Gryffindor")
let ronWeasley = new Student("Ron", "Weasley", "Gryffindor")

 

 

harryPotter와 ronWeasley의 이름, 성, 그리고 기숙사에는 아래와 같이 접근할 수 있다.

 

harryPotter.firstName // "Harry"
harryPotter.LastName // "Potter"
harryPotter.house // "Gryffindor"

 

 

인스턴스 메소드 vs. 정적 메소드

인스턴스 메소드(또는 프로퍼티 메소드)란 클래스의 새로이 만들어진 인스턴스가 자유롭게 사용할 수 있는 그 클래스의 메소드를 뜻한다. 즉, Sword 예제에선 attack()과 defend()가 이에 해당하는 것이었다. 

 

class Sword {
  constructor(name, color, damage) {
    this.name = name;
    this.color = color;
    this.damage = damage;
  }
  
  attack() {
    //공격
  }
  
  defend() {
    // 방어
  }
}

// Sword 클래스의 인스턴스, 강력한 장미칼 만들기
let roseKnife = new Sword('Rose Knife', 'Rose Pattern', 999999999999)

// Sword 클래스의 메소드를 우리의 강력크한 장미칼이 사용할 수 있다
roseKnife.attack()
roseKnife.defend()

 

 

new 키워드를 사용해서 만든 모든 인스턴스들은 자신을 만드는데 모티브가 되 준 클래스의 인스턴스 메소드를 사용할 수 있다. 또 다른 예시로 호그와트 버전을 보도록 하자. 아래 예시는 자기소개를 하는 introduce() 메소드와 기숙사 점수를 추가하는 earnHousePonts() 메소드를 추가한 예시이다. (기숙사 점수를 나타내기 위해 housePoints 프로퍼티도 추가했다)

 

 

class Student {
  constructor(firstName, lastName, house) {
    this.firstName = firstName
    this.lastName = lastName
    this.house = house
    this.housePoints = 0;
  }
  introduce() {
    return `My name is ${this.firstName} ${this.lastName}.`
  }
  earnHousePoints() {
    this.housePoints += 1
    return `${this.firstName} ${this.lastName} has earned points for ${this.house}!`
  }
}

let harry = new Student("Harry", "Potter", "Gryffindor")

harry.introduce() // "My name is Harry Potter."

harry.earnHousePoints() // "Harry Potter has earned points for Gryffindor!"
harry.housePoints // 1

 

위 예시를 보면, Student 클래스의 인스턴스인 harry는 아무런 제약없이 Student 클래스의 메소드인 introduce()와 earnHousePoints()를 사용할 수 있다. 이러한 메소드들을 인스턴스 메소드 또는 프로토타입 메소드라고 하는 것이다.

 

반면에, 정적 메소드(static method)라 불리는 것들은 인스턴스가 사용할 수 없는 메소드들을 뜻한다. 아래 예시에서, 정적 메소드는 일반 메소드들과는 다르게 앞에 static이라는 키워드가 붙은 함수들을 뜻한다.

 

class Student {
  constructor(firstName, lastName, house) {
    this.firstName = firstName
    this.lastName = lastName
    this.house = house
    this.housePoints = 0;
  }
  introduce() {
    return `My name is ${this.firstName} ${this.lastName}.`
  }
  earnHousePoints() {
    this.housePoints += 1
    return `${this.firstName} ${this.lastName} has earned points for ${this.house}!`
  }
  static startOWL() {
    return `OWL will start in 10 minutes.`
  }
}

let harry = new Student("Harry", "Potter", "Gryffindor")

harry.startOWL() //Uncaught TypeError: firstStudent.startOWL is not a function at...
Student.startOWL() // "OWL will start in 10 minutes."

 

위 예시에서 볼 수 있는 것과 같이, harry 인스턴스는 static 키워드가 사용된 startOWL() 메소드를 사용할 수 없다. 왜냐하면, 이와 같은 정적 메소드는 클래스 본체만이 호출할 수 있기 때문이다. 그렇기 때문에 Student.startOWL()은 에러 없이 작동이 된다.

 

정적 메소드는 이와 같이 인스턴스로 호출이 불가능하다. 또한, 정적 메소드는 클래스 정의 이후 인스턴스라 별도로 생성되지 않아도 호출이 가능하다. MDN을 보면, 정적 메소드는 주로 앱의 유틸리티 기능을 추가하는 데에 사용한다고 설명한다.

 


"The static keyword defines a static method or property for a class. Neither static methods nor static properties can be called on instances of the class. Instead, they're called on the class itself.

 Static methods are often utility functions, such as functions to create or clone objects, whereas static properties are useful for caches, fixed-configuration, or any other data you don't need to be replicated across instances."

"static 키워드는 클래스의 정적인 메소드 또는 프로퍼티를 정의한다. 정적 메소드와 정적 프로퍼티는 클래스의 인스턴스로부터 호출될 수 없다. 대신, 클래스 그 자체로 호출을 해야한다.

정적 메소드는 객체를 생성하거나 복제하는 기능과 같은 유틸리티 기능에 사용된다. 정적 프로퍼티는 캐시, 고정된 설정, 또는 각 인스턴스마다 복제할 필요가 없는 데이터 등을 만들때 유용하다." (출처: 링크)

 

 

 

서브클래스(Subclass)란?

서브클래스란 기존의 다른 클래스의 속성과 메소드를 그대로 상속받아 쓸 수 있으면서, 추가로 새로운 상속과 메소드를 더해서 만들 수 있는 클래스를 뜻한다. 객체 지향 프로그래밍 패러다임의 기본 원칙 중 상속(inheritance)과 다향성(polymorphism)을 대변하는 행위라고도 볼 수 있다. 다음과 같은 예제를 생각해보자.

 

// 수퍼클래스
class Magician {
  constructor(name) {
    this.name = name
  }
  attack() {
    return '마법 발사!!!'
  }
  heal() {
    return `${this.name} 손은 약손 네 배는 똥배`
  }
}

// 서브클래스
class FireMagician extends Magician {
   constructor(name) { //만약 수퍼클래스의 프로퍼티를 그대로 가져오는 경우라면 생략이 가능하다
     super(name)
   }
   attack() {
    return `${super.attack()} 통구이가 되어라!!!`
  }
}

let josh = new FireMagician('Josh')

josh.heal() // "Josh 손은 약손 네 배는 똥배"
josh.attack() // "마법 발사!!! 통구이가 되어라!!!"

 

super는 함수처럼 호출이 가능하면서 this와 같이 식별자로도 참조할 수 있는 키워드이다. super를 사용하면 수퍼클래스(= 상위 클래스)의 메소드 및 수퍼클래스의 constructor를 호출할 수 있다. 만약, 새로 생성하는 서브클래스가 기존 수퍼클래스의 모든 프로퍼티를 그대로 가진 경우라면 서브클래스의 constructor를 생략할 수 있다.

 

그러나 위 예제같은 경우엔 수퍼클래스의 메소드를 변경했기 때문에 서브클래스의 constructor를 생략할 수 없다. 그리고, 이와 같이 서브클래스에서 constructor를 생략하지 않은 경우엔, 서브클래스의 constructor에서는 꼭 super를 함께 호출해주어야 한다.

 

서브클래스의 코드 중, attack() 부분을 보자. 그 안엘 보면 super.attack()을 사용한 것을 볼 수 있다. 맨 밑에 줄 josh.attack()의 결과물에서 볼 수있는 것과 같이, 메소드 안에서 super를 부르면 수퍼클래스의 메소드를 호출할 수 있다.

 

 

 

728x90
반응형

댓글