TypeScript 클래스 기초
class User {
// 속성 선언 (TypeScript 필수)
id: number;
name: string;
private email: string; // private: 클래스 내부만 접근
protected role: string; // protected: 상속 클래스까지
readonly createdAt: Date; // readonly: 재할당 불가
constructor(id: number, name: string, email: string) {
this.id = id;
this.name = name;
this.email = email;
this.role = "user";
this.createdAt = new Date();
}
getEmail(): string {
return this.email; // private이지만 클래스 내부에서 접근 가능
}
}
const user = new User(1, "철수", "kim@example.com");
console.log(user.name); // ✅
// console.log(user.email); // ❌ private
생성자 단축 문법
// 반복적인 this.xxx = xxx를 줄이는 방법
class User {
readonly createdAt: Date = new Date();
constructor(
public id: number,
public name: string,
private email: string,
protected role: string = "user",
) {}
getEmail() { return this.email; }
}
접근 제어자 비교
flowchart LR
subgraph PUBLIC["public (기본값)"]
P["어디서나 접근 가능"]
end
subgraph PRIVATE["private"]
PR["클래스 내부만"]
end
subgraph PROTECTED["protected"]
PRT["클래스 내부 + 상속 클래스"]
end
상속
class AdminUser extends User {
constructor(
id: number,
name: string,
email: string,
private permissions: string[],
) {
super(id, name, email); // 부모 생성자 호출
this.role = "admin"; // protected이므로 접근 가능
}
hasPermission(perm: string): boolean {
return this.permissions.includes(perm);
}
}
const admin = new AdminUser(2, "관리자", "admin@example.com", ["delete", "edit"]);
console.log(admin.hasPermission("delete")); // true
인터페이스 구현
interface Serializable {
serialize(): string;
deserialize(data: string): void;
}
interface Loggable {
log(): void;
}
class Document implements Serializable, Loggable {
constructor(
public title: string,
public content: string,
) {}
serialize(): string {
return JSON.stringify({ title: this.title, content: this.content });
}
deserialize(data: string): void {
const parsed = JSON.parse(data);
this.title = parsed.title;
this.content = parsed.content;
}
log(): void {
console.log(`[Document] ${this.title}`);
}
}
추상 클래스
인스턴스를 직접 만들 수 없고, 반드시 상속해서 사용합니다.
abstract class Shape {
abstract getArea(): number; // 반드시 구현해야 함
abstract getPerimeter(): number;
// 구현된 메서드는 상속됨
describe(): string {
return `넓이: ${this.getArea()}, 둘레: ${this.getPerimeter()}`;
}
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
getArea(): number {
return Math.PI * this.radius ** 2;
}
getPerimeter(): number {
return 2 * Math.PI * this.radius;
}
}
// const shape = new Shape(); // ❌ 추상 클래스 직접 인스턴스화 불가
const circle = new Circle(5);
console.log(circle.describe());
static 멤버
class Counter {
private static count = 0;
static increment(): void {
Counter.count++;
}
static getCount(): number {
return Counter.count;
}
}
Counter.increment();
Counter.increment();
console.log(Counter.getCount()); // 2
실전: 제네릭 Repository 패턴
interface Entity {
id: number;
}
abstract class Repository<T extends Entity> {
protected items: T[] = [];
findById(id: number): T | undefined {
return this.items.find(item => item.id === id);
}
save(item: T): void {
const index = this.items.findIndex(i => i.id === item.id);
if (index >= 0) {
this.items[index] = item;
} else {
this.items.push(item);
}
}
findAll(): T[] {
return [...this.items];
}
abstract validate(item: T): boolean;
}
class UserRepository extends Repository<User> {
validate(user: User): boolean {
return user.name.length > 0;
}
}
정리
| 제어자 | 접근 범위 |
|---|---|
public | 어디서든 |
private | 클래스 내부만 |
protected | 클래스 + 상속 클래스 |
readonly | 재할당 불가 |
static | 인스턴스 없이 접근 |
abstract | 구현 강제 |
다음 편에서는 유틸리티 타입 — Partial, Required, Readonly, Pick, Omit 등 내장 타입 변환 도구를 배웁니다.