타입스크립트 Interface VS Abstract Class

Min Su Kwon·2024년 2월 12일

Typescript

목록 보기
1/1

Interface

인터페이스는 타입스크립트에서 타입을 정의하는 방법으로, 타입스크립트의 일반적인 타입과는 달리 확장이 가능하고 중복 선언이 가능하다는 차이점이 있다.

인터페이스를 선언해 추상클래스의 모양(멤버와 프로퍼티)을 정의해놓고, 클래스를 선언할 때 implements 키워드를 사용해서 해당 인터페이스의 모양을 따르도록 강제할 수 있다.

interface IAnimal {
  name: string;
  roar: () => void;
}

class Lion implements IAnimal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  roar() {
    console.log('grrr')
  }
}

class Cat implements IAnimal {
  name: string;
  
  constructor(name: string) {
    this.name = name;
  }
  
  roar() {
    console.log('meow')
  }
}

const lion = new Lion('lion');
const cat = new Cat('cat');

lion.roar();
cat.roar();

만약 인터페이스에 정의해놓은 시그니쳐를 따르지 않는다면, 에러가 발생함을 알 수 있다.

class ICat implements IAnimal {
  name: number;
  
  constructor(name: number) {
    this.name = name;
  }
  
  roar() {
    console.log('meow')
  }
}

Abstract Class

Abstract Class는 일반적으로 선언된 클래스 앞에 abstract 키워드를 추가하여 선언할 수 있는 말그대로 추상 클래스이며, 생성자를 통해 인스턴스를 생성할 수 없으나, 다른 클래스가 extends 키워드를 통해 상속받을 수 있다.

abstract class CAnimal {
  private name: string;

  constructor(name: string) {
    this.name = name;
  }

  public abstract roar(): void;
}

class Lion extends CAnimal {
  roar() {
    console.log('grrr')
  }
}

class Cat extends CAnimal {
  roar() {
    console.log('meow')
  }
}

const lion = new Lion('lion');
const cat = new Cat('cat');

lion.roar();
cat.roar();

Interface VS Abstract Class - 차이점

세부 구현

추상 클래스는 세부 구현을 포함할 수 있으며, 추상 메서드를 선언할 경우 상속받는 클래스가 반드시 해당 메서드를 직접 구현하도록 강제할 수 있다. 반면 인터페이스는 클래스의 모양을 정의할 뿐, 세부 구현을 선언할수는 없다.

abstract class CAnimal {
  private name: string;

  constructor(name: string) {
    this.name = name;
  }

  public abstract roar(): void;

  public introduce(): void {
    console.log(`my name is ${this.name}`);
  }
}

class Lion extends CAnimal {
  roar() {
    console.log('grrr')
  }

  override introduce() {
    console.log('GRRRRRRRRRR')
  }
}

class Cat extends CAnimal {
  roar() {
    console.log('meow')
  }
}

const lion = new Lion('lion');
const cat = new Cat('cat');

lion.roar(); // grrr
lion.introduce(); // GRRRRRRRRRR

cat.roar(); // meow
cat.introduce(); // my name is cat

접근 제한자

추상 클래스는 일반적으로 OOP에서 사용하는 public, private, protected와 같은 접근 제한자를 사용할 수 있다. 인터페이스는 모양만을 지정하는 것이기에 접근 제한자를 지정할 수 없다.

abstract class CAnimal {
  private name: string;

  constructor(name: string) {
    this.name = name;
  }

  public abstract roar(): void;

  protected introduce(): void {
    console.log(`my name is ${this.name}`);
  }
}

타입스크립트 컴파일 결과와 런타임의 차이

실제 타입스크립트가 컴파일되어 자바스크립트 코드로 변환되었을 때, 인터페이스는 흔적도 없이 사라지게 된다. 반면 추상 클래스는 멀쩡히 클래스로써 남아있으며, 따라서 정상적으로 런타임에 instanceof의 피연산자로 사용될 수 있다. 아래는 추상 클래스의 컴파일 결과로, CAnimal 클래스가 그대로 남아있고, instanceof도 정상적으로 동작함을 확인할 수 있다.

"use strict";
class CAnimal {
    constructor(name) {
        this.name = name;
    }
}
class Lion extends CAnimal {
    roar() {
        console.log('grrr');
    }
}
class Cat extends CAnimal {
    roar() {
        console.log('meow');
    }
}
const lion = new Lion('lion');
const cat = new Cat('cat');
lion.roar();
cat.roar();
console.log(cat instanceof CAnimal);

반면 인터페이스의 경우 자바스크립트 코드로 변환되는 과정에서 완전히 사라지며, 따라서 런타임에 인터페이스와 관련된 작업은 할 수 없다. 아래는 인터페이스의 컴파일 결과로, IAnimal이 완전히 사라졌음을 확인할 수 있다.

"use strict";
class Lion {
    constructor(name) {
        this.name = name;
    }
    roar() {
        console.log('grrr');
    }
}
class Cat {
    constructor(name) {
        this.name = name;
    }
    roar() {
        console.log('meow');
    }
}
const lion = new Lion('lion');
const cat = new Cat('cat');
lion.roar();
cat.roar();

결론

인터페이스는 단순하게 모양을 정의해주는 역할만을 한다. OOP의 추상클래스와 상속을 구현하는 것이 목적이라면, 당연하게도 Abstract Class를 통해 구현하는 것이 맞다.

References

profile
이제 막 커리어를 시작한 소프트웨어 엔지니어입니다. 배운 것을 정리하면서 조금 더 깊이 이해하려는 습관을 들이려고 합니다. 피드백은 언제나 환영입니다.

0개의 댓글