[TypeScript-04] Class와 Interface

Comely·2025년 3월 5일

TypeScript

목록 보기
4/9

TypeScript Class와 Interface 완전 가이드

Class 기본 개념

Class란?

객체(Object)를 생성하는 템플릿 또는 복사기계

JavaScript vs TypeScript Class

// JavaScript (ES6)
class Person {
  constructor() {
    this.name = 'kim'  // 바로 사용 가능
    this.age = 20
  }
}
// TypeScript (에러 발생)
class Person {
  constructor() {
    this.name = 'kim'  // 에러! Property 'name' does not exist
    this.age = 20      // 에러! Property 'age' does not exist
  }
}

TypeScript 특징: 속성을 미리 선언해야 함


Class 필드값 타입 지정

기본 필드값 선언

class Person {
  data: number = 0  // 타입 지정 + 기본값
  name: string
  age: number
}

let john = new Person()
let kim = new Person()

console.log(john.data)  // 0
console.log(kim.data)   // 0

필드값 타입 지정 방법

class Student {
  name: string = 'kim'
  age: number = 20
  married: boolean = false
  subjects: string[] = ['math', 'english']
}

Constructor 함수 타입 지정

Constructor 기본 구조

class Person {
  name: string      // 1. 필드값 미리 선언
  age: number
  
  constructor(name: string, age: number) {  // 2. 매개변수 타입 지정
    this.name = name  // 3. 필드에 할당
    this.age = age
  }
}

let person1 = new Person('kim', 25)
let person2 = new Person('park', 30)

Constructor 타입 지정 규칙

  1. 필드 미리 선언: this.속성 사용 전 반드시 필드로 선언
  2. 매개변수 타입 지정: 함수와 동일하게 타입 지정
  3. return 타입 지정 불가: 항상 객체를 반환하므로 의미 없음

실전 예제

class Car {
  model: string
  price: number
  
  constructor(model: string, price: number) {
    this.model = model
    this.price = price
  }
  
  // 메서드 추가
  tax(): number {
    return this.price * 0.1
  }
}

let car1 = new Car('소나타', 3000)
console.log(car1)         // { model: '소나타', price: 3000 }
console.log(car1.tax())   // 300

Class 메서드 타입 지정

기본 메서드 타입 지정

class Calculator {
  add(x: number, y: number): number {
    return x + y
  }
  
  subtract(x: number, y: number): number {
    return x - y
  }
  
  printResult(result: number): void {
    console.log(`결과: ${result}`)
  }
}

let calc = new Calculator()
console.log(calc.add(5, 3))     // 8
calc.printResult(calc.add(5, 3)) // 결과: 8

메서드에서 this 사용

class BankAccount {
  private balance: number = 0
  
  deposit(amount: number): void {
    this.balance += amount
  }
  
  withdraw(amount: number): boolean {
    if (this.balance >= amount) {
      this.balance -= amount
      return true
    }
    return false
  }
  
  getBalance(): number {
    return this.balance
  }
}

고급 Constructor 패턴

Rest Parameter 활용

class Word {
  num: number[]
  str: string[]
  
  constructor(...params: (number | string)[]) {
    let 숫자들: number[] = []
    let 문자들: string[] = []
    
    params.forEach((param) => {
      if (typeof param === 'string') {
        문자들.push(param)
      } else {
        숫자들.push(param)
      }
    })
    
    this.num = 숫자들
    this.str = 문자들
  }
}

let obj = new Word('kim', 3, 5, 'park')
console.log(obj.num)  // [3, 5]
console.log(obj.str)  // ['kim', 'park']

Interface 기본 개념

Interface란?

객체의 구조를 정의하는 타입 선언 방식

Type Alias vs Interface

// Type Alias 방식
type Square1 = {
  color: string,
  width: number
}

// Interface 방식
interface Square2 {
  color: string
  width: number
}

let 네모1: Square1 = { color: 'red', width: 100 }
let 네모2: Square2 = { color: 'blue', width: 200 }

공통점: 기능과 용도가 거의 동일
차이점: 확장 방법과 중복 선언 처리가 다름


Interface 확장 (extends)

기본 확장

interface Student {
  name: string
}

interface Teacher extends Student {
  age: number
}

// Teacher는 name과 age 속성을 모두 가짐
let teacher: Teacher = {
  name: 'kim',
  age: 30
}

다중 확장

interface Name {
  name: string
}

interface Age {
  age: number
}

interface Person extends Name, Age {
  married: boolean
}

let person: Person = {
  name: 'kim',
  age: 25,
  married: false
}

Type vs Interface 차이점

1. 확장 방법

// Interface - extends 사용
interface Animal {
  name: string
}
interface Cat extends Animal {
  legs: number
}

// Type - & (intersection) 사용
type Animal2 = {
  name: string
}
type Cat2 = Animal2 & { legs: number }

2. 중복 선언

// Interface - 중복 선언 허용 (병합됨)
interface Animal {
  name: string
}
interface Animal {
  legs: number
}
// Animal = { name: string, legs: number }

// Type - 중복 선언 불가 (에러)
type Animal3 = { name: string }
type Animal3 = { legs: number }  // 에러!

3. 사용 권장사항

상황권장
객체 타입 정의Interface
라이브러리 타입 정의Interface (확장성)
Union 타입Type
유틸리티 타입Type
엄격한 타입 정의Type

Interface 고급 활용

함수가 포함된 Interface

interface MathOperations {
  plus: (a: number, b: number) => number
  minus: (a: number, b: number) => number
  multiply?: (a: number, b: number) => number  // 선택적 속성
}

let calculator: MathOperations = {
  plus(a, b) {
    return a + b
  },
  minus(a, b) {
    return a - b
  }
}

배열 타입 Interface

interface Product {
  product: string
  price: number
}

interface CartItem extends Product {
  quantity: number
}

let 장바구니: Product[] = [
  { product: '청소기', price: 7000 },
  { product: '삼다수', price: 800 }
]

let 상세장바구니: CartItem[] = [
  { product: '청소기', price: 7000, quantity: 1 },
  { product: '삼다수', price: 800, quantity: 3 }
]

실전 예제

예제 1: 쇼핑몰 상품 관리

interface BaseProduct {
  brand: string
  serialNumber: number
  model: string[]
}

interface Electronics extends BaseProduct {
  warranty: number  // 보증기간 (개월)
  powerConsumption: number  // 전력소비량
}

interface Clothing extends BaseProduct {
  size: 'S' | 'M' | 'L' | 'XL'
  color: string
}

let tv: Electronics = {
  brand: 'Samsung',
  serialNumber: 1360,
  model: ['TV', 'QLED'],
  warranty: 24,
  powerConsumption: 120
}

let shirt: Clothing = {
  brand: 'Nike',
  serialNumber: 5678,
  model: ['T-shirt', 'Cotton'],
  size: 'M',
  color: 'blue'
}

예제 2: 게임 캐릭터 시스템

interface Character {
  name: string
  level: number
  hp: number
}

interface Mage extends Character {
  mp: number
  spells: string[]
  castSpell(spellName: string): void
}

interface Warrior extends Character {
  armor: number
  weapons: string[]
  attack(target: string): void
}

class MageCharacter implements Mage {
  name: string
  level: number = 1
  hp: number = 100
  mp: number = 50
  spells: string[] = ['fireball', 'heal']
  
  constructor(name: string) {
    this.name = name
  }
  
  castSpell(spellName: string): void {
    console.log(`${this.name}${spellName}을 시전합니다!`)
    this.mp -= 10
  }
}

예제 3: API 응답 타입 정의

interface ApiResponse<T> {
  success: boolean
  data?: T
  error?: string
  timestamp: number
}

interface User {
  id: number
  username: string
  email: string
}

interface Post {
  id: number
  title: string
  content: string
  authorId: number
}

let userResponse: ApiResponse<User> = {
  success: true,
  data: {
    id: 1,
    username: 'john_doe',
    email: 'john@example.com'
  },
  timestamp: Date.now()
}

let postsResponse: ApiResponse<Post[]> = {
  success: true,
  data: [
    { id: 1, title: '첫 글', content: '내용...', authorId: 1 },
    { id: 2, title: '둘째 글', content: '내용...', authorId: 1 }
  ],
  timestamp: Date.now()
}

Class와 Interface 결합

implements 키워드

interface Flyable {
  fly(): void
}

interface Swimmable {
  swim(): void
}

class Duck implements Flyable, Swimmable {
  fly(): void {
    console.log('오리가 날아갑니다')
  }
  
  swim(): void {
    console.log('오리가 수영합니다')
  }
}

class Fish implements Swimmable {
  swim(): void {
    console.log('물고기가 수영합니다')
  }
}

Abstract Class와 Interface

interface Shape {
  calculateArea(): number
}

abstract class AbstractShape implements Shape {
  abstract calculateArea(): number
  
  displayInfo(): void {
    console.log(`면적: ${this.calculateArea()}`)
  }
}

class Circle extends AbstractShape {
  constructor(private radius: number) {
    super()
  }
  
  calculateArea(): number {
    return Math.PI * this.radius ** 2
  }
}

class Rectangle extends AbstractShape {
  constructor(private width: number, private height: number) {
    super()
  }
  
  calculateArea(): number {
    return this.width * this.height
  }
}

에러 처리와 디버깅

자주 발생하는 에러

1. 필드 미선언 에러

// 에러 발생
class Person {
  constructor(name: string) {
    this.name = name  // 에러! Property 'name' does not exist
  }
}

// 올바른 방법
class Person {
  name: string  // 필드 선언 필수
  
  constructor(name: string) {
    this.name = name
  }
}

2. Interface 중복 속성 에러

// 에러 발생
interface Animal {
  name: string
}
interface Dog extends Animal {
  name: number  // 에러! 타입이 다름
}

// 올바른 방법
interface Animal {
  name: string
}
interface Dog extends Animal {
  breed: string  // 새로운 속성 추가
}

모범 사례

1. 네이밍 컨벤션

// Interface: 대문자 시작, 명사형
interface UserProfile { }
interface ApiResponse<T> { }

// Class: 대문자 시작, 명사형
class UserManager { }
class DatabaseConnection { }

// Type: 대문자 시작
type StatusType = 'loading' | 'success' | 'error'

2. 파일 구조

// types/user.ts
export interface User {
  id: number
  name: string
  email: string
}

export interface UserCreateRequest {
  name: string
  email: string
  password: string
}

// classes/userManager.ts
import { User, UserCreateRequest } from '../types/user'

export class UserManager {
  private users: User[] = []
  
  createUser(request: UserCreateRequest): User {
    // 구현...
  }
}

3. Generic 활용

interface Repository<T> {
  findById(id: number): T | null
  save(entity: T): T
  delete(id: number): boolean
}

class UserRepository implements Repository<User> {
  private users: User[] = []
  
  findById(id: number): User | null {
    return this.users.find(user => user.id === id) || null
  }
  
  save(user: User): User {
    this.users.push(user)
    return user
  }
  
  delete(id: number): boolean {
    const index = this.users.findIndex(user => user.id === id)
    if (index > -1) {
      this.users.splice(index, 1)
      return true
    }
    return false
  }
}

실전 체크리스트

Class 사용법

  • 필드값 미리 선언
  • Constructor 매개변수 타입 지정
  • 메서드 타입 지정 (매개변수 + 반환값)
  • this 키워드 올바른 사용

Interface 사용법

  • 객체 구조 명확히 정의
  • extends로 재사용성 높이기
  • 선택적 속성(?) 적절히 활용
  • 함수 타입 정확히 지정

설계 원칙

  • Interface로 계약 정의
  • Class로 구현체 작성
  • 단일 책임 원칙 준수
  • 확장 가능한 구조 설계
profile
App, Web Developer

0개의 댓글