TypeScript의 클래스는 JavaScript 클래스에 접근 제어자, 추상 클래스, 인터페이스 구현 등 타입 안전 기능을 추가한 것입니다.

접근 제어자(Access Modifiers)

TYPESCRIPT
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)

생성자에서 접근 제어자를 붙이면 자동으로 속성이 됩니다.

TYPESCRIPT
// 간결한 방식
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

TYPESCRIPT
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 — 인터페이스 구현

TYPESCRIPT
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의 주의점

TYPESCRIPT
interface HasName {
  name: string;
}

class Person implements HasName {
  // implements는 타입을 "부여"하는 게 아니라 "검사"만 함
  name: string; // 직접 선언해야 함

  constructor(name: string) {
    this.name = name;
  }
}

abstract 클래스 — 추상 클래스

직접 인스턴스를 만들 수 없고, ** 상속해서 구현을 강제 **하는 클래스입니다.

TYPESCRIPT
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

TYPESCRIPT
// 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

TYPESCRIPT
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는 구조적 타이핑 을 따르므로, 클래스 이름이 달라도 구조가 같으면 호환됩니다.

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 클래스도 구조적 타이핑을 따른다
댓글 로딩 중...