안녕하세요. 김용성입니다.
제가 지난 포스팅 TypeScript개론 5편에서 Class에 대해 설명드렸는데요.
저의 경우 Interface와 Class가 많이 헷갈렸었어요. 여러분들도 그런가요?
그래서 이번 포스팅에서는 TypeScript에서 Interface와 Class의 차이점에 대해 설명드리고자 합니다.
인터페이스는 ES6가 지원하지 않는 타입스크립트만의 특징입니다. 인터페이스는 타입이며 컴파일 후에 사라진다. 추상 클래스는 선언과 구현이 모두 존재하지만 인터페이스는 선언만 존재하며, 멤버 변수와 멤버 메서드를 선언할 수 있지만 접근 제한자는 설정할 수 없습니다.
클래스와 달리 interface는 TypeScript의 컨텍스트 내에서만 존재하는 가상 구조입니다. TypeScript 컴파일러는 타입 체크 목적으로만 인터페이스를 사용합니다. 코드가 JavaScript 언어로 트랜스 파일되면 인터페이스에서 제거됩니다.
클래스는 ES6에서 JavaScript 생태계에 공식적으로 도입되었습니다.
객체지향 프로그래밍은 커다란 문제를 클래스라는 단위로 나누고 클래스 간의 관계를 추가하면서 코드 중복을 최소화하는 개발 방식입니다. 클래스는 객체지향 프로그래밍 그 자체이며 클래스 간의 관계는 상속이나 포함 관계를 고려해 추가합니다.
- 커스텀 타입으로 사용됩니다. 인터페이스에는 필수 항목으로 사용할 프로퍼티를 선언해서, 함수에 전달하는 인자의 형식을 고정할 수 있습니다. 이 경우는 인터페이스를 인자로 받는 함수가 실행될 때 TypeScript 컴파일러가 인자의 유효성을 먼저 검사합니다.
- 추상클래스로 사용됩니다. 메소드의 형태만 선언해서 인터페이스를 정의하고, 이후에 클래스를 정의할 때 implements 키워드를 사용하면서 이 인터페이스를 지정하면, 이 클래스는 추상함수로 선언된 메소드의 몸체를 받아들여야 합니다. 인터페이스를 정의하는 예를 살펴보겠습니다.
//app.ts
interface Pizza {
name: string;
size: string[];
}
let pizza: Pizza;
function createPizza(name: string, sizes: string[]) {
return {
name,
sizes
}
}
pizza = createPizza('Pepperoni', ['small', 'medium']);
++ implements 활용법 🤗
// app.ts
interface ClockInterface {
currentTime: Date;
}
class Clock implements ClockInterface {
public currentTime: Date;
// private _currentTime: Date; // Error!
}
const digital: ClockInterface = new Clock();
이렇게 인터페이스를 기반으로 클래스를 생성할 수 있습니다.
- 객체 팩토리로 사용됩니다. Class는 객체의 모양과 동작에 대한 청사진을 정의한 다음 클래스 속성을 초기화하고 method를 정의합니다. 따라서 클래스의 인스턴스를 만들 때 실행 가능한 함수와 정의된 property를 가진 객체를 얻습니다. 클래스를 정의하는 예를 살펴 보겠습니다.
//app.ts
class PizzaMaker {
static create(event: { name: string; toppings: string[] }) {
return { name: event.name, toppings: event.toppings };
}
}
클래스를 이용할 경우 별도 인스턴스를 만들지 않고도 static 함수를 사용하여 인스턴스를 만들 수 있다는 장점이 있습니다. 다음과 같이 create함수를 호출할 수 있습니다. (Interface에서는 타입체크만 가능할 뿐 이러한 것이 불가능합니다.)
// app.ts
const pizza = PizzaMaker.create({
name: 'Inferno',
toppings: ['cheese', 'peppers'],
});
console.log(pizza);
만약 static을 적용시키지 않았다면 다음과 같이 피자 인스턴스를 생성해야합니다.
// app.ts
class PizzaMaker {
create(event: { name: string; toppings: string[] }) {
return { name: event.name, toppings: event.toppings };
}
}
const pizzaMaker = new PizzaMaker();
const pizza = pizzaMaker.create({
name: 'Inferno',
toppings: ['cheese', 'peppers'],
});
- Interface를 사용하는 경우 : 코드에서 둘 이상의 위치, 특히 둘 이상의 파일 또는 함수에서 사용될 객체의 속성 및 함수를 생성해야하는 경우에 사용합니다. 특정 유형의 객체들이 동일한 속성을 가지고 있을때 Interface를 통해 이를 쉽게 구현할 수 있습니다. (ex: Teacher-Math Teacher/English Teacher~~~)
- Interface를 사용하지 않는 경우 : 타입 체크 뿐만 아니라 constructor 또는 함수의 구현를 원할 때는 Class를 사용하는 것이 옳습니다. (Implements를 사용하여 Class에 내려주는 방법도 존재하긴 합니다.)
- Class를 사용하는 경우 : 함수를 포함하고 있는 객체를 만들다보면 초기화를 위한 constructor이 있는 경우가 있습니다. 이 경우에는 Class를 사용하는 것이 옳습니다. 또한 new를 사용하여 인스턴스를 만드려고 할때 Class를 통해 이를 간편화 시킬 수 있습니다. 마지막으로 간단한 데이터 객체의 경우에도 Class를 사용하여 기본값을 설정할 수 있습니다. 필요한 경우 Interface를 사용할 수 있지만 Interface는 오로지 타입 체크 용도로 사용됩니다.
- Class를 사용하지 않는 경우 : 단순히 Type alias에서 벗어나 타입 체크를 용이하게 하려고 하는 경우 Class를 사용하는 것보다 Interface를 사용하는 것이 효율적입니다.
만약 여러분이 new키워드를 쓰지 않고 상속의 구현만 할것이라면 Interface를 사용해도 무방합니다.
또한 함수나 클래스는 할당되는 문법이 따로 있기 때문에 보통 객체에서 Interface를 많이 사용합니다.
오늘의 포스팅은 이것으로 마치겠습니다.
긴 글 읽어주셔서 감사합니다:)