ex) 체스
// 종류가 많지 않을 때는 모든 값을 타입 리터럴로 직접 열거 가능
type Color = 'Black' | 'White'
type File = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H'
type Rank = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8
class Position {
// 자동으로 매개변수를 this에 할당(=> this.file, this.rank)
// private: Position 인스턴스 외부에서 접근 불가
constructor (private file: File, private rank: Rank) {
}
}
// abstract: 사용자가 Piece 인스턴스를 직접 생성하지 못하게 막음
// 대신 Piece 클래스를 상속받은 클래스를 통해서만 인스턴스화 가능
abstract class Piece {
// Piece 인스턴스, Piece의 서브클래스 인스턴스에서만 접근 가능
protected position: Position
// readonly: 초기에 값을 할당한 다음 더 이상 값을 덮어쓸 수 없게 함
constructor (private readonly color: Color, file: File, rank: Rank) {
// 생성자에서 position 값을 할당하지 않으면
// position 타입을 Position 또는 undefined가 될 수 있도록 시그니처를 바꾸어야 함
this.position = new Position(file, rank)
}
moveTo(position: Position) {
this.position = position
}
// 상속받은 클래스에서 해당 메서드를 반드시 구현해야함
abstract canMoveTo(position: Position): boolean
distanceFrom(position: Position) {
return {
rank: Math.abs(position.rank - this.rank),
file: Math.abs(position.file.charCodeAt(0) - this.file.charCodeAt(0))
}
}
}
class King extends Piece {
canMoveTo(position: Position) {
let distance = this.position.distanceFrom(position)
return distance.rank < 2 && distance.file < 2
}
}
// 새 게임을 만들 때 자동으로 보드와 말 생성
class Game {
private pieces = Game.makePieces()
private static makePieces() {
return [
new King('White', 'E', 1),
new King('Black', 'E', 8),
...
]
}
}
💜 super로 부모 클래스의 메서드에만 접근할 수 있고 프로퍼티에는 접근할 수 없음
class Set {
has(value: number): boolean {
...
}
add(value: number): this {
...
}
}
: 타입 별칭처럼 타입에 이름을 지어주는 수단
: 타입 별칭와 인터페이스는 문법만 다를 뿐 거의 같은 기능 수행
// 타입 별칭
type Food {
calories: number
tasty: boolean
}
type Sushi = Food & {
salty: boolean
}
type Cake = Food & {
sweet: boolean
}
// 인터페이스
interface Food {
calories: number
tasty: boolean
}
interface Sushi extends Food {
salty: boolean
}
interface Cake extends Food {
sweet: boolean
}
💙 인터페이스는 객체 타입, 클래스, 다른 인터페이스 모두를 상속받을 수 있음
interface Animal {
// 접근 한정자(private, protected, public), static 키워드 사용x
readonly name: string
eat(food: string): void
sleep(hours: number): void
}
// Cat 클래스는 Animal이 선언하는 모든 메서드를 구현해야 함
// 필요 시 메서드나 프로퍼티 추가 구현 가능
class Cat implements Animal {
name = 'nabi'
eat(food: string) {
console.info(`Ate some ${food}. Mmm!`)
}
sleep(hours: number) {
console.info(`Slept for ${hours} hours`)
}
}
// 한 클래스가 여러 인터페이스 구현 가능
class Cat implements Animal, Feline {
...
}
인터페이스 vs 추상 클래스
인터페이스
: 형태를 정의하는 수단
: 객체, 배열, 함수, 클래스, 클래스 인스턴스 정의 가능
: 아무런 자바스크립트 코드를 만들지 않으며 컴파일 타임에만 존재
추상 클래스
: 오직 클래스만 정의 가능
: 런타임의 자바스크립트 코드를 만듦
: 생성자와 기본 구현을 가질 수 있으며 프로퍼티와 메서드에 접근 한정자를 지정할 수 있음
💛 여러 클래스에서 공유하는 구현 => 추상 클래스 추천
💛 가볍게 '이 클래스는 T다'라고 말하고 싶다면 인터페이스 추천
class Zebra {
trot() { ... }
}
class Poodle {
trot() { ... }
}
function ambleAround(animal: Zebra) {
animal.trot()
}
let zebra = new Zebra
let poodle = new Poodle
ambleAround(zebra)
ambleAround(poodle)
class A {
private x = 1
}
class B extends A { }
function f(a: A) { }
f(new A)
f(new B)
f({ x: 1 }) // Error: 인수 '{x: number}' 타입은 매개변수 'A' 타입에 할당할 수 없음
✅ 클래스는 타입으로도 선언 가능! class MyMap<K, V> {
// constructor에는 제네릭 타입 선언 불가 => 대신 class 선언에 사용
constructor(initialKey: K, initialValue: V) {
...
}
get(key: K): V {
...
}
set(key: K, value: V): void {
...
}
merge<K1, V1>(map: MyMap<K1, V1>): MyMap<K | K1, V | V1> {
...
}
static of<K, V>(k: K, v: V): MyMap<K, V> {
...
}
}
// 명시
let a = new MyMap<string, number>('k', 1)
// 추론
let b = new MyMap('k', true)