인터페이스는 타입스크립트에서 타입을 정의하는 방법으로, 타입스크립트의 일반적인 타입과는 달리 확장이 가능하고 중복 선언이 가능하다는 차이점이 있다.
인터페이스를 선언해 추상클래스의 모양(멤버와 프로퍼티)을 정의해놓고, 클래스를 선언할 때 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 키워드를 추가하여 선언할 수 있는 말그대로 추상 클래스이며, 생성자를 통해 인스턴스를 생성할 수 없으나, 다른 클래스가 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();
추상 클래스는 세부 구현을 포함할 수 있으며, 추상 메서드를 선언할 경우 상속받는 클래스가 반드시 해당 메서드를 직접 구현하도록 강제할 수 있다. 반면 인터페이스는 클래스의 모양을 정의할 뿐, 세부 구현을 선언할수는 없다.
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를 통해 구현하는 것이 맞다.