클래스와 타입 — public, private, abstract, implements
TypeScript의 클래스는 JavaScript 클래스에 접근 제어자, 추상 클래스, 인터페이스 구현 등 타입 안전 기능을 추가한 것입니다.
접근 제어자(Access Modifiers)
class User {
public name: string; // 어디서든 접근 가능 (기본값)
private password: string; // 클래스 내부에서만 접근 가능
protected email: string; // 클래스와 상속받은 클래스에서 접근 가능
readonly id: number; // 읽기 전용 — 생성자에서만 할당
constructor(name: string, password: string, email: string) {
this.id = Date.now();
this.name = name;
this.password = password;
this.email = email;
}
// private 메서드
private hashPassword(): string {
return `hashed_${this.password}`;
}
// public 메서드 (기본값)
getProfile(): string {
return `${this.name} (${this.email})`;
}
}
const user = new User('홍길동', '1234', 'hong@test.com');
user.name; // OK
// user.password; // ❌ Error — private
// user.email; // ❌ Error — protected
// user.id = 999; // ❌ Error — readonly
매개변수 속성(Parameter Properties)
생성자에서 접근 제어자를 붙이면 자동으로 속성이 됩니다.
// 간결한 방식
class User {
constructor(
public name: string,
private password: string,
protected email: string,
readonly id: number = Date.now(),
) {}
}
// 위와 동일한 긴 방식
class UserLong {
public name: string;
private password: string;
protected email: string;
readonly id: number;
constructor(name: string, password: string, email: string) {
this.name = name;
this.password = password;
this.email = email;
this.id = Date.now();
}
}
TypeScript private vs JavaScript #private
class Example {
private tsPrivate: string = 'TS private'; // 컴파일 타임만 보호
#jsPrivate: string = 'JS private'; // 런타임에서도 보호
}
const ex = new Example();
// 둘 다 직접 접근 불가이지만...
// (ex as any).tsPrivate; // ⚠️ 런타임에서 접근 가능 (any로 우회)
// (ex as any).#jsPrivate; // ❌ 진짜 접근 불가 (SyntaxError)
면접에서 이 차이를 물어보더라고요. TypeScript의 private은 컴파일 타임에만 동작하고, JavaScript의 #은 런타임에서도 진짜 비공개입니다.
implements — 인터페이스 구현
interface Printable {
print(): void;
}
interface Serializable {
serialize(): string;
}
// 여러 인터페이스를 동시에 구현
class Document implements Printable, Serializable {
constructor(private content: string) {}
print(): void {
console.log(this.content);
}
serialize(): string {
return JSON.stringify({ content: this.content });
}
}
// 인터페이스를 만족하지 않으면 에러
// class BadDoc implements Printable {
// // Error: Property 'print' is missing
// }
implements의 주의점
interface HasName {
name: string;
}
class Person implements HasName {
// implements는 타입을 "부여"하는 게 아니라 "검사"만 함
name: string; // 직접 선언해야 함
constructor(name: string) {
this.name = name;
}
}
abstract 클래스 — 추상 클래스
직접 인스턴스를 만들 수 없고, ** 상속해서 구현을 강제 **하는 클래스입니다.
abstract class Shape {
abstract area(): number; // 반드시 구현해야 할 메서드
abstract perimeter(): number;
// 일반 메서드는 구현을 가질 수 있음
describe(): string {
return `넓이: ${this.area()}, 둘레: ${this.perimeter()}`;
}
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
area(): number {
return Math.PI * this.radius ** 2;
}
perimeter(): number {
return 2 * Math.PI * this.radius;
}
}
// const shape = new Shape(); // ❌ Error — 추상 클래스는 인스턴스화 불가
const circle = new Circle(5);
console.log(circle.describe()); // '넓이: 78.54, 둘레: 31.42'
abstract vs interface
// interface — 구현 없이 구조만 정의
interface Animal {
name: string;
makeSound(): string;
}
// abstract class — 공통 구현을 포함할 수 있음
abstract class AnimalBase {
constructor(public name: string) {}
abstract makeSound(): string; // 추상 메서드
describe(): string { // 공통 구현
return `${this.name}: ${this.makeSound()}`;
}
}
class Dog extends AnimalBase {
makeSound(): string {
return '멍멍';
}
}
면접 포인트: abstract class는 공통 로직을 공유할 때, interface는 구조 계약만 필요할 때 사용합니다. TypeScript는 다중 상속이 안 되지만 다중 implements는 가능합니다.
getter와 setter
class Temperature {
private _celsius: number;
constructor(celsius: number) {
this._celsius = celsius;
}
// getter
get fahrenheit(): number {
return this._celsius * 9 / 5 + 32;
}
// setter
set fahrenheit(f: number) {
this._celsius = (f - 32) * 5 / 9;
}
get celsius(): number {
return this._celsius;
}
set celsius(c: number) {
if (c < -273.15) {
throw new Error('절대 영도 이하는 불가');
}
this._celsius = c;
}
}
const temp = new Temperature(100);
console.log(temp.fahrenheit); // 212
temp.fahrenheit = 32;
console.log(temp.celsius); // 0
클래스와 구조적 타이핑
TypeScript는 구조적 타이핑 을 따르므로, 클래스 이름이 달라도 구조가 같으면 호환됩니다.
class Point {
constructor(public x: number, public y: number) {}
}
class Coordinate {
constructor(public x: number, public y: number) {}
}
// 구조가 같으므로 호환됨
const p: Point = new Coordinate(1, 2); // OK!
정리
public(기본),private,protected로 접근을 제어한다- TypeScript
private은 컴파일 타임만 보호, JS#은 런타임까지 보호한다 implements는 인터페이스 구조를 만족하는지 검사한다abstract클래스는 직접 인스턴스화 불가하며 상속해서 구현을 강제한다- TypeScript 클래스도 구조적 타이핑을 따른다
댓글 로딩 중...