본문 바로가기

웹 프론트엔드

JavaScript로 추상 클래스, 추상 메소드를 구현하기

0. 서문

ES6 부터 JavaScript에서 class 문법을 지원하게 되었다.

그러면서 JavaScript로도 객체지향 프로그래밍을 하기 수월해졌다. (이전에는 생성자 함수와 prototype으로 구현하였다.)

그렇지만 여전히 Java와 같은 언어 만큼 객체지향을 지원한다고 할 수 없다.

이를테면, JavaScript에서는 추상 클래스와 추상 메소드를(abstract) 문법적으로 지원하지 않는다.

그럼에도 JavaScript 기본 문법을 활용하여 추상 클래스와 추상 메소드를 구현하는 방법을 알아보겠다.

 

1. 추상 클래스와 추상 메소드의 개념

먼저 추상 클래스와 추상 메소드의 개념에 대해 알아보자.

 

추상 메소드란 부모 클래스에서 정의하며, 자식 클래스에서 반드시 오버라이딩해야만 사용할 수 있는 메소드를 말한다.

그러므로 추상 메소드는 구현부가 없고, 선언부만이 존재한다.

 

추상 클래스는 추상 메소드를 하나 이상 포함한 클래스이며, 정의되지 않은 추상 메소드를 포함하고 있으므로, 인스턴스를 생성할 수 없다.

그러므로 상속만이 가능하다.

 

핵심을 정리하자면,

1. 추상 메소드는 반드시 오버라이딩 되어야만 사용 가능하다.

2. 추상 클래스는 인스턴스를 생성할 수 없고, 상속만 가능하다.

class Animal {
  constructor(name) {
    this.name = name;
  }

  move() {
    console.log('동물이 움직입니다');
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name);
  }
}

일반적인 JavaScript 클래스 문법이다.


Animal은 "동물"을 의미하는데, "동물"이라는 생명은 존재하지 않고, 추상적인 관념이므로, 추상 클래스라고 볼 수 있다.
new Animal()로 인스턴스를 생성할 일은 없을 것이고, 그래서는 안 된다.

 

그런데, 위 코드 하에서 무리없이 가능하다.

 

또한, 모든 동물은 move할 것이므로, 자식 클래스는 move 메소드를 필수적으로 정의하고 사용했으면 좋겠다.
아무런 문법을 추가하지 않고는, Dog 클래스에서 move를 불러와 사용할 수 있다.

 

JavaScript에서 위에서 요약한 2가지 제약을 가해보자.

 

1. 추상 메소드는 반드시 오버라이딩 되어야만 사용 가능하다.

2. 추상 클래스는 인스턴스를 생성할 수 없고, 상속만 가능하다.

 

2. 추상 클래스 구현

먼저 2번은 이와 같이 구현해볼 수 있다.

class Animal {
  constructor(name) {
    this.name = name;
    if (this.constructor === Animal) {
      throw new Error("추상 클래스로 인스턴스를 생성하였습니다.");
    }
  }

  move() {
    console.log('동물이 움직입니다');
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name);
  }
}

const animal = new Animal("동물"); // this.constructor === [class Animal]
const dog = new Dog("개"); // this.constructor === [class Dog extends Animal]

this.constructor는 부모 클래스 명을 return 한다.
그러므로 Animal을 인스턴스로 생성하면, [class Animal]가 되고,

Dog를 인스턴스로 생성하면 [class Dog extends Animal]가 된다.

 

이를 이용하여 Animal을 인스턴스로 생성하면 Error를 호출한다.

 

3. 추상 메소드 구현

다음으로 추상 메소드가 오버라이딩 되지 않고는 사용이 불가능하도록 해보자.

class Animal {
  constructor(name) {
    this.name = name;
    if (this.constructor === Animal) {
      throw new Error("추상 클래스로 인스턴스를 생성하였습니다.");
    }
  }

  move() {
    throw new Error("추상 메소드는 꼭 오버라이딩 되어야 합니다.");
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name);
  }

  move() {
    console.log("웕 웕");
  }
}

const dog = new Dog("개");
dog.move();

이처럼 추상 메소드에서 Error를 던져준다.
그러면, 자식 클래스에서 move를 오버라이딩하지 않고 사용하면 Error가 호출될 것이다.
move를 재정의한 후에야 move를 사용할 수 있다.

 

4. 부족한 점

추상 메소드는 본디, 자식 클래스로 하여금 필수적으로 구현하도록 하는 것에 의미가 있다.

하지만, 위 방법으로 추상 메소드를 구현하지 않아도 에러를 내지 않고, 추상 메소드를 구현하지 않고 호출했을 때 에러를 낸다는 점에서

완벽하게 구현한 것은 아니다.