타입스크립트 클래스 심층 탐구
안녕하세요! 타입스크립트 시리즈 네 번째 시간입니다. 오늘은 타입스크립트에서 객체 지향 프로그래밍의 핵심인 **클래스(Class)**에 대해 자세히 알아보겠습니다. ES6에서 도입된 자바스크립트 클래스를 타입스크립트는 더욱 강력하게 확장합니다.
기본 클래스 구문
타입스크립트에서 기본적인 클래스 선언은 다음과 같습니다:
class Person {
// 프로퍼티
name: string;
age: number;
// 생성자
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// 메서드
greet(): string {
return `안녕하세요, 제 이름은 ${this.name}이고 ${this.age}살입니다.`;
}
}
// 클래스 인스턴스 생성
const person = new Person("홍길동", 30);
console.log(person.greet()); // "안녕하세요, 제 이름은 홍길동이고 30살입니다."
접근 제한자(Access Modifiers)
타입스크립트는 세 가지 접근 제한자를 제공합니다:
- public: 어디서나 접근 가능 (기본값)
- private: 클래스 내부에서만 접근 가능
- protected: 클래스 내부와 상속받은 하위 클래스에서 접근 가능
class Employee {
public name: string; // 어디서나 접근 가능
private salary: number; // 클래스 내부에서만 접근 가능
protected department: string; // 상속받은 클래스에서도 접근 가능
constructor(name: string, salary: number, department: string) {
this.name = name;
this.salary = salary;
this.department = department;
}
// private 멤버에 접근하는 public 메서드
public getSalary(): number {
return this.salary;
}
}
const employee = new Employee("김영희", 5000000, "개발");
console.log(employee.name); // "김영희" - public이므로 접근 가능
// console.log(employee.salary); // 오류: 'salary'는 private이므로 'Employee' 클래스 외부에서 접근할 수 없습니다.
// console.log(employee.department); // 오류: 'department'는 protected이므로 'Employee' 클래스 및 하위 클래스 외부에서 접근할 수 없습니다.
console.log(employee.getSalary()); // 5000000 - public 메서드를 통해 접근 가능
생성자 매개변수 약식 표기
클래스의 프로퍼티를 선언하고 생성자에서 할당하는 패턴은 매우 일반적이어서, 타입스크립트는 이를 간결하게 표현할 수 있는 방법을 제공합니다:
// 약식 표기를 사용한 동일한 Employee 클래스
class Employee {
constructor(
public name: string,
private salary: number,
protected department: string
) {
// 자동으로 프로퍼티 선언 및 초기화됨
}
public getSalary(): number {
return this.salary;
}
}
읽기 전용 프로퍼티(Readonly Properties)
초기화 후 변경할 수 없는 프로퍼티를 선언할 수 있습니다:
class Car {
readonly model: string;
readonly brand: string;
color: string;
constructor(brand: string, model: string, color: string) {
this.brand = brand;
this.model = model;
this.color = color;
}
changeColor(newColor: string): void {
this.color = newColor; // 가능
// this.model = "New Model"; // 오류: readonly 프로퍼티에 할당할 수 없음
}
}
const myCar = new Car("Tesla", "Model 3", "red");
myCar.color = "blue"; // 가능
// myCar.brand = "BMW"; // 오류: readonly 프로퍼티를 변경할 수 없음
상속(Inheritance)
클래스는 다른 클래스를 상속받아 기능을 확장할 수 있습니다:
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
makeSound(): void {
console.log("일반적인 동물 소리");
}
}
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name); // 부모 클래스의 생성자 호출
this.breed = breed;
}
// 메서드 오버라이딩
makeSound(): void {
console.log("멍멍!");
}
// 추가 메서드
fetch(): void {
console.log(`${this.name}이(가) 물건을 가져옵니다!`);
}
}
const dog = new Dog("바둑이", "진돗개");
console.log(dog.name); // "바둑이"
console.log(dog.breed); // "진돗개"
dog.makeSound(); // "멍멍!"
dog.fetch(); // "바둑이이(가) 물건을 가져옵니다!"
추상 클래스(Abstract Classes)
추상 클래스는 직접 인스턴스화할 수 없고, 다른 클래스가 상속받아 구현해야 하는 기본 클래스입니다:
abstract class Shape {
color: string;
constructor(color: string) {
this.color = color;
}
// 추상 메서드 - 하위 클래스에서 반드시 구현해야 함
abstract calculateArea(): number;
// 일반 메서드
displayColor(): void {
console.log(`이 도형의 색상은 ${this.color}입니다.`);
}
}
class Circle extends Shape {
radius: number;
constructor(color: string, radius: number) {
super(color);
this.radius = radius;
}
// 추상 메서드 구현
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
}
// const shape = new Shape("red"); // 오류: 추상 클래스는 인스턴스화할 수 없음
const circle = new Circle("빨강", 5);
console.log(circle.calculateArea()); // 약 78.54
circle.displayColor(); // "이 도형의 색상은 빨강입니다."
인터페이스 구현(Implementing Interfaces)
클래스는 인터페이스를 구현하여 특정 구조를 준수하도록 강제할 수 있습니다:
interface Movable {
speed: number;
move(): void;
}
interface Resizable {
resize(width: number, height: number): void;
}
// 여러 인터페이스 구현
class GameCharacter implements Movable, Resizable {
name: string;
speed: number;
width: number;
height: number;
constructor(name: string, speed: number, width: number, height: number) {
this.name = name;
this.speed = speed;
this.width = width;
this.height = height;
}
move(): void {
console.log(`${this.name}이(가) ${this.speed}의 속도로 이동합니다.`);
}
resize(width: number, height: number): void {
this.width = width;
this.height = height;
console.log(`${this.name}의 크기가 ${width}x${height}로 변경되었습니다.`);
}
}
const character = new GameCharacter("전사", 10, 50, 100);
character.move(); // "전사이(가) 10의 속도로 이동합니다."
character.resize(60, 120); // "전사의 크기가 60x120로 변경되었습니다."
정적 프로퍼티와 메서드(Static Properties and Methods)
인스턴스가 아닌 클래스 자체에 속하는 프로퍼티와 메서드를 정의할 수 있습니다:
class MathUtils {
// 정적 프로퍼티
static PI: number = 3.14159;
// 정적 메서드
static add(x: number, y: number): number {
return x + y;
}
static calculateCircleArea(radius: number): number {
return MathUtils.PI * radius * radius;
}
}
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.add(5, 3)); // 8
console.log(MathUtils.calculateCircleArea(5)); // 약 78.54
게터와 세터(Getters and Setters)
프로퍼티 접근을 제어하기 위한 게터와 세터를 정의할 수 있습니다:
class BankAccount {
private _balance: number;
constructor(initialBalance: number = 0) {
this._balance = initialBalance;
}
// 게터
get balance(): number {
return this._balance;
}
// 세터
set balance(amount: number) {
if (amount < 0) {
throw new Error("잔액은 0보다 작을 수 없습니다.");
}
this._balance = amount;
}
// 일반 메서드
deposit(amount: number): void {
if (amount <= 0) {
throw new Error("입금액은 0보다 커야 합니다.");
}
this._balance += amount;
}
withdraw(amount: number): void {
if (amount <= 0) {
throw new Error("출금액은 0보다 커야 합니다.");
}
if (amount > this._balance) {
throw new Error("잔액이 부족합니다.");
}
this._balance -= amount;
}
}
const account = new BankAccount(1000);
console.log(account.balance); // 1000 - 게터를 통해 접근
account.balance = 2000; // 세터를 통해 설정
console.log(account.balance); // 2000
account.deposit(500);
console.log(account.balance); // 2500
account.withdraw(1000);
console.log(account.balance); // 1500
// account.balance = -100; // 오류: "잔액은 0보다 작을 수 없습니다."
제네릭 클래스(Generic Classes)
다양한 타입에 대해 재사용 가능한 클래스를 만들 수 있습니다:
class Queue<T> {
private items: T[] = [];
enqueue(item: T): void {
this.items.push(item);
}
dequeue(): T | undefined {
return this.items.shift();
}
peek(): T | undefined {
return this.items[0];
}
get length(): number {
return this.items.length;
}
}
// 문자열 큐
const stringQueue = new Queue<string>();
stringQueue.enqueue("Hello");
stringQueue.enqueue("TypeScript");
console.log(stringQueue.dequeue()); // "Hello"
console.log(stringQueue.length); // 1
// 숫자 큐
const numberQueue = new Queue<number>();
numberQueue.enqueue(10);
numberQueue.enqueue(20);
console.log(numberQueue.peek()); // 10
console.log(numberQueue.length); // 2
믹스인(Mixins)
타입스크립트에서는 여러 클래스의 기능을 하나의 클래스로 결합하는 믹스인 패턴을 구현할 수 있습니다:
// 믹스인 클래스들
class Timestamped {
timestamp = Date.now();
}
class Activatable {
isActive = false;
activate() {
this.isActive = true;
}
deactivate() {
this.isActive = false;
}
}
// 믹스인을 적용할 베이스 클래스
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
// 믹스인 함수
type Constructor<T = {}> = new (...args: any[]) => T;
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
timestamp = Date.now();
};
}
function Activatable<TBase extends Constructor>(Base: TBase) {
return class extends Base {
isActive = false;
activate() {
this.isActive = true;
}
deactivate() {
this.isActive = false;
}
};
}
// 믹스인 적용
const TimestampedUser = Timestamped(User);
const TimestampedActivatableUser = Activatable(TimestampedUser);
// 사용
const user = new TimestampedActivatableUser("Alice");
console.log(user.name); // "Alice"
console.log(user.timestamp); // 현재 타임스탬프
console.log(user.isActive); // false
user.activate();
console.log(user.isActive); // true
마치며
타입스크립트 클래스는 자바스크립트 클래스의 모든 기능을 포함하면서도, 정적 타입 검사와 추가적인 기능들을 제공합니다. 접근 제한자, 추상 클래스, 인터페이스 구현 등을 통해 더 안전하고 구조화된 객체 지향 코드를 작성할 수 있습니다.
다음 포스팅에서는 타입스크립트의 제네릭(Generics)에 대해 자세히 알아보겠습니다. 타입스크립트 시리즈를 계속 지켜봐 주세요!