목차
TypeScript에서 추상 클래스(abstract class)와 인터페이스(interface)는 둘 다 “규약”을 정의하는 도구이다. 하지만 실제 목적과 사용법은 다르다.
이번 글에서는 직원 조직도 비유를 통해 이 차이를 쉽게 정리해보고자 한다.
추상 클래스 vs 인터페이스 — 핵심 차이
인터페이스 (interface)
- 컴파일 후 사라짐: 타입 정보만 제공, JS 출력 없음.
- 구조적 타이핑: 이름이 아니라 모양(shape) 이 같으면 호환.
- 다중 구현 가능:
class A implements I1, I2
- 선언 병합 가능: 같은 이름의 인터페이스를 여러 번 정의하면 자동으로 합쳐짐.
- 상태(필드) 불가: 런타임 상태나 공통 구현은 가질 수 없음.
추상 클래스 (abstract class)
- 런타임에 남음: 공통 메서드, 필드, 생성자 등이 JS로 출력됨.
- 단일 상속만 가능:
extends
는 하나뿐. - 부분 구현 가능: 공통 로직을 구현하면서 일부 메서드는
abstract
로 강제. - 상태/접근제어자 지원:
protected
,private
멤버를 정의 가능.
정리: “공통 구현 + 상태 공유”가 필요하다면 추상 클래스, “모양(계약)”만 필요하다면 인터페이스.
의사결정 체크리스트
- 공통 로직을 실제로 공유하고 싶은가?
→ 추상 클래스 - 객체/함수의 모양만 강제하고 싶은가?
→ 인터페이스 - 여러 계약을 동시에 강제하고 싶은가?
→ 클래스는 추상 클래스 하나extends
, 여러 인터페이스implements
- 타입을 점진적으로 확장하고 싶은가?
→ 인터페이스 (선언 병합 지원) - 트리 셰이킹/번들 크기 중요?
→ 인터페이스 (런타임에 코드가 남지 않음)
직원 조직도 예시로 이해해보기
추상 클래스 = 모든 직원이 공유하는 속성과 기능
회사를 생각해 봅시다. 직원이라면 누구나 공통적으로 출근과 퇴근을 한다. 또한 직원마다 출근 시간과 퇴근 시간 같은 공통 속성을 갖는다.이런 공통 속성과 기능을 정의하는 데 적합한 것이 바로 추상 클래스다.
abstract class Employee {
constructor(
public name: string,
public startTime: string,
public endTime: string
) {}
abstract work(): void; // 일하는 방식은 직원마다 다름
clockIn() {
console.log(`${this.name} 출근: ${this.startTime}`);
}
clockOut() {
console.log(`${this.name} 퇴근: ${this.endTime}`);
}
}
Employee
추상 클래스는 모든 직원이 공통으로 가지는 필드와 메서드를 담고 있다.- 하지만
work()
같은 메서드는 직원마다 달라질 수 있으니abstract
로 정의해 구현을 강제한다.
인터페이스 = 역할(포지션)마다 필요한 기능 명세
이제 직원들은 포지션에 따라 다른 일을 한다.
- 정규직 직원은 회의 참석이 필요할 수 있고,
- 계약직 직원은 보고서 제출이 필요할 수 있다.
이처럼 포지션마다 추가되는 계약(규칙)은 인터페이스로 표현한다.
interface RegularWorker {
attendMeeting(): void;
}
interface ContractWorker {
submitReport(): void;
}
- 인터페이스는 “이 포지션이라면 반드시 이런 기능을 가져야 한다”라는 약속을 정의한다.
- 런타임에는 존재하지 않고, 타입 검사 용도로만 쓰인다.
추상 클래스 + 인터페이스 조합
이제 실제 직원 클래스를 만들어 보자.
class RegularEmployee extends Employee implements RegularWorker {
work() {
console.log(`${this.name}이(가) 팀 프로젝트를 진행합니다.`);
}
attendMeeting() {
console.log(`${this.name}이(가) 회의에 참석합니다.`);
}
}
class ContractEmployee extends Employee implements ContractWorker {
work() {
console.log(`${this.name}이(가) 맡은 업무를 수행합니다.`);
}
submitReport() {
console.log(`${this.name}이(가) 보고서를 제출합니다.`);
}
}
사용 예시:
const kim = new RegularEmployee("김철수", "09:00", "18:00");
kim.clockIn(); // 공통 기능 (추상 클래스)
kim.work(); // 직원마다 다름
kim.attendMeeting(); // 정규직 인터페이스 기능
kim.clockOut();
const lee = new ContractEmployee("이영희", "10:00", "17:00");
lee.clockIn();
lee.work();
lee.submitReport(); // 계약직 인터페이스 기능
lee.clockOut();
정리
- 추상 클래스(Employee)
- 모든 직원이 공유하는 속성(출퇴근 시간)과 기능(출근/퇴근) 정의
- 특정 기능(
work
)은 “각자 다르게” 구현하도록 강제
- 인터페이스(RegularWorker, ContractWorker)
- 각 포지션(정규직/계약직)이 반드시 수행해야 하는 추가 기능 계약 정의
- 런타임에는 사라지고, 타입 검사에만 사용됨
한마디로 다음과 같이 정리할 수 있다.
- 추상 클래스 = 직원 전체의 공통 기반
- 인터페이스 = 포지션별 역할 명세
경계 사례
- Java와 달리 TypeScript 인터페이스는 구현을 가질 수 없다. (자바의
default
메서드와 다름) - type vs interface: 객체 모양만 정의할 때는 둘 다 가능. 다만
interface
는 선언 병합이 가능하고,type
은 유니온/조건부 타입 표현이 유리. - 구조적 타이핑 주의: 의도치 않게 모양만 같아도 호환되므로, 공개 API에는 꼭 필요한 속성만 노출하는 게 좋다.
마무리
- 인터페이스: 타입 계약만, 컴파일 후 사라짐
- 추상 클래스: 공통 상태/구현 공유, 런타임에도 존재
- 선택 규칙: “공통 로직 공유 → 추상 클래스, 계약만 정의 → 인터페이스”
TypeScript에서 추상 클래스와 인터페이스는 대체제가 아니다.
공통된 속성과 구현을 공유하고 싶다면 추상 클래스, 역할별로 어떤 기능이 필요하다고 약속만 하고 싶다면 인터페이스를 먼저 고려해보자.
공식 문서 참고
'Language > TypeScript' 카테고리의 다른 글
[TypeScript] infer (0) | 2025.09.13 |
---|---|
[TypeScript] ERROR: TS2550 tsconfig.json이 무시되는 이유와 해결 방법 (0) | 2025.09.12 |