TypeScript - 클래스

euNung·2022년 7월 20일
0

TypeScript

목록 보기
4/8

클래스와 상속

  • 한정자
    • public
      : 어디에서나 접근 가능
      : 기본적으로 주어지는 접근 수준
    • protected
      : 해당 클래스와 서브클래스의 인스턴스에서만 접근 가능
    • private
      : 해당 클래스의 인스턴스에서만 접근 가능

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
    : 자식 클래스가 부모 클래스에 정의된 메서드를 오버라이드하면 자식 인스턴스는 super를 이용해 부모 버전의 메서드 호출 가능
    • 방법1) 메서드 호출
      ex) super.take
    • 방법2) 생성자 함수에서 호출
      : 자식 클래스에 생성자 함수가 있다면 super()를 호출해야 부모 클래스와 정상적으로 연결됨

💜 super로 부모 클래스의 메서드에만 접근할 수 있고 프로퍼티에는 접근할 수 없음

  • this를 반환타입으로 사용
class Set {
  has(value: number): boolean {
    ...
  }
  add(value: number): this {
    ...
  }
}

Interface

: 타입 별칭처럼 타입에 이름을 지어주는 수단
: 타입 별칭와 인터페이스는 문법만 다를 뿐 거의 같은 기능 수행

// 타입 별칭
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
}

💙 인터페이스는 객체 타입, 클래스, 다른 인터페이스 모두를 상속받을 수 있음

  • implements
    : 클래스를 선언할 때 implements 키워드를 이용해 특정 인터페이스를 만족시킴을 표현
    : 어댑터(adapter), 팩토리(factory), 전략(strategy) 등 디자인 패턴을 구현하는 대표적인 방식
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)
    • 예외) private이나 protected 필드를 갖는 클래스
       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) 
profile
프론트엔드 개발자

0개의 댓글