TypeScriptTypeScript 기초 · 5기초

클래스와 접근 제어자 — TypeScript OOP

TypeScript클래스OOP접근제어자추상클래스

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 등 내장 타입 변환 도구를 배웁니다.

궁금한 점이 있으신가요?

협업·의뢰는 아래로, 가벼운 소통은 인스타그램 @bluefox._.hi도 환영이에요.