dev_eunz

[타입스크립트] 클래스와 인터페이스 본문

IT

[타입스크립트] 클래스와 인터페이스

은그램 2022. 8. 21. 20:19
728x90
반응형

클래스와 인터페이스

C#과 타입스크립트의 클래스의 기능 유사

  • 클래스와 상속
    • 타입지정타입 안정성을 어느정도 확보 가능지정한 값 이외의 값을 사용하면 컴파일 에러 발생
    • type Color = 'Black' | 'White' type File = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' type Rank = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8
    • Color / File / Rank 라는 변수에 값을 제한할 수 있음
    • 접근 한정자
      • public
      • 어디에서나 접근 가능
      • private자동으로 매개변수를 this에 할당
      • 서브클래스 인스턴스에는 접근 불허
      • 접근 한정자
      • protected서브클래스 인스턴스 모두에 접근 허용
      • private처럼 프로퍼티를 this에 할당
      • readonlykotlin으로 치면, val / java로 치면 final
      • 초기 값 할당 후, 더 이상 값을 덮어쓸 수 없음
    • 이를 통해 내부 구현 정보를 너무 공개하지 않고, 잘 정의된 API만 노출하도록 클래스 설계 가능
    • abstract아래와 같이 직접 인스턴스화 하려고 하면 에러 발생 ( 추상 클래스의 인스턴스 생성 불가 )필요한 메서드를 추상 클래스에 자유롭게 추가 가능이와 같이 bbb 메서드 추가했을 때,추상 클래스를 구현할 때는, 반드시 추상 메서드도 구현해야 함
    • Test를 상속받은 클래스가 bbb 메서드를 구현하지 않으면 컴파일 타임에 에러 발생
    • abstract class Test { aaa(testType: TestType) { this.testType = testType } abstract bbb(testType: TestType): boolean }
    • new Piece('White', 'E', 1) // Error
    • 상속받은 클래스를 통해서만 인스턴스화 할 수 있도록 허용
    class 키워드로 클래스 선언 후 extends 키워드로 다른 클래스 상속 받을 수 있음
    • 클래스
      • 구체 클래스
      • 추상 클래스
      • 추상 메서드, 추상 프로퍼티 가질 수 있음
      • 인스턴스 프로퍼티도 가질 수 있음private / protected / public 중 한 가지 한정자 가질 수 있음
      • readonly 추가할 수 있음
      • 기본값: public
      생성자의 매개변수 / 프로퍼티 초기자에도 한정자 사용 가능
    • 메서드private / protected / public 중 한 가지 한정자 가질 수 있음
      • 인스턴스 메서드
      • 정적 메서드
    • 기본값: public
    • super메서드에만 접근가능, 프로퍼티 접근 불가능
      • super.methodName
      • super() : 생성자 함수에서만 호출할 수 있음
    • 자식 클래스가 부모 클래스에 정의된 메서드를 오버라이드 하면, 자식 인스턴스는 super를 통해 부모 버전의 메서드를 호출 가능
    • this를 반환 타입으로 사용
      1. Set 이라는 클래스 생성
      class Set {
      	has(value: number): boolean { ...	}
      	add(value: number): Set { ... }
      }
      
      1. Set 을 상속받는 클래스 생성
      class MutableSet extends Set {
      	delete(value: number): boolean { ... }
      	add(value: number): MutableSet { ... }
      }
      
      ⇒ 상속받는 서브클래스는 this를 반환하는 모든 메서드의 시그니처를 오버라이드 해야함
      class Set {
      	has(value: number): boolean { ... }
      	add(value: number): this { ... }
      }
      
      this를 통해서 MutableSet 클래스 생성시에도 add 메서드를 오버라이드 할 필요가 없어짐
    • 이럴 때, this를 사용
    • 인터페이스타입별칭 ( type ) 과 유사이와 동일한 인터페이스Meat 타입별칭을 사용한 모든 곳에 인터페이스 대신 사용 가능
      type Food = {
      	calories: number
      	tasty: boolean
      }
      
      type Meat = Food & {
      	salty: boolean
      }
      type Cookie = Food & {
      	sweet: boolean
      }
      
      인터페이스로 하면,※ 인터페이스는 객체타입, 클래스, 다른 인터페이스 모두를 상속받을 수 있음
      1. 타입별칭의 경우, 타입 연산자를 포함한 모든 타입이 등장
      2. 아래와 같은 경우, 인터페이스로 재작성 불가
      type A = number
      type B = A | string
      
      1. 인터페이스를 상속할 때, 상속받는 인터페이스 타입에 상위 인터페이스를 할당할 수 있는지 확인
      interface A {
      	good(x: number): string
      	bad(x: number): string
      }
      
      interface B extends A {
      	good(x: string | number): string
      	bad(x: string): string
      }
      
      아래와 같은 경우, bad 메서드에 A에서는 number를 사용하였기에 B에 있는 string 타입에 할당할 수 없음( 코드 편집기로 확인 가능 )
      1. 이름과 범위가 같은 인터페이스가 여러개 있다면 이들이 자동으로 합쳐짐⇒ 선언 합침
        • 선언 합침
          interface User {
          	name: string
          }
          
          interface User {
          	age: number
          }
          
          위와같이 두 개의 인터페이스를 선언하면, 아래와 같이 자동으로 합쳐짐그래서 아래와 같이 사용할 수 있게 됨이를 타입별칭으로 사용하면, 오류가 나게 됨
          interface User {
          	age: string
          }
          
          interface User {
          	age: number
          }
          
          제네릭을 선언한 인터페이스의 경우, 제네릭들의 선언 방법과 이름까지 똑같아야 합칠 수 있음
          interface User<Age extends number> {
          	age: Age
          }
          
          interface User<Age extends string> {
          	age: Age
          }
          
        • 아래와 같이 제네릭 타입이 다르다면, 오류 발생
        • 인터페이스에서 오류가 나는 경우
        • let a: User = { name: 'Eunji', age: 19 }
        • interface User { name: string age: number }
        • 같은 이름으로 정의된 여러 정의를 자동으로 합침
      2. 같은 조건에서 타입 별칭이 여러개라면, 컴파일 에러가 남
      클래스 선언할 때 implements 라는 키워드를 통해 인터페이스 표현
      • 예시Cat은 Animal이 선언하는 모든 메서드를 구현해야 함
      • 필요하다면 메서드나 프로퍼티 추가 구현 가능
      • interface Animal { eat(food: string): void sleep(hours: number): void } class Cat implements Animal { eat(food: string) { console.info('Ate some', food, '. Mmm!') } sleep(hours: number) { console.info('Slept for', hours, 'hours') } }
      가시성 한정자 (private, protected, public), static 키워드는 선언할 수 없음한 클래스에 여러 인터페이스 구현 가능
      • 예시
      • interface Animal { readonly name: string eat(food: string): void sleep(hours: number): void } interface Feline { meow(): void } class Cat implements Animal, Feline { name = 'Whiskers' eat(food: string) { console.info('Ate some', food, '.Mmm!') } sleep(hours: number) { console.info('Slept for', hours, 'hours') } meow() { console.info('Meow') } }
      프로퍼티를 빼먹거나 구현에 문제가 있으면 타입스크립트가 바로 알려줌
    • 객체 타입처럼 인스턴스 프로퍼티(지역변수)를 readonly로 설정 가능
    • 오버로드한 메서드가 작성됨
    • interface Food { calories: number tastuy: boolean } interface Meat extends Food { salty: boolean } interface Cookie extends Food { sweet: boolean }
    • 타입 조합
    • interface Meat { calories: number salty: boolean tasty: boolean }
    • type Meat = { calories: number salty: boolean tasty: boolean }
    • 타입에 이름을 지어주는 수단
    • 인터페이스 구현 vs 추상 클래스 상속인터페이스가 더 범용으로 쓰임
      • 인터페이스객체, 배열, 함수, 클래스, 클래스 인스턴스를 정의할 수 있음
      • 컴파일 타임에만 존재
      • 형태를 정의하는 수단
      • 추상 클래스런 타임의 자바스크립트 클래스 코드 만듬
        • 인터페이스와 차별점프로퍼티와 메서드에 접근 한정자 지정 가능
        • 생성자와 기본 구현을 가질 수 있음
      • 오직 클래스만 정의 가능
    • 추상 클래스는 특별한 목적과 풍부한 기능을 가짐
    • 둘은 아주 비슷
    • 타입스크립트는 클래스 비교시 구조 기준으로 비교 ★ ( 타 언어와 차이점 )
      • 예시
      • class Zebra { trot() { ... } } class Poodle { trot() { ... } } function ambleAround(animal: Zebra) { animal.trot() } let zebra = new Zebra let poodle = new Poodle ambleAround(zebra) ambleAround(poodle) // OK. 에러 없음
      private / protected 필드를 갖는 클래스는 위의 예시와 다른 결과를 가져옴
      • 예시{x: 1} 은 private가 아니기 때문에 할당할 수 없음
      • class A { private x = 1 } class B extends A {} function f(a: A) {} f(new A) // OK f(new B) // OK f({x: 1}) // ERROR
    • 클래스는 값과 타입을 모두 선언타입 추론 기능 덕분에 컴패니언 타입 같은 기능 구현 가능
      • 타입 추론 예시
      • // 값 let a = 1999 function b() {} // 타입 type a = number interface b { (): void } ... if(a + 1 > 3) // 문맥상 값 a(1999)로 추론 let x: a = 3 // 문맥상 타입 a(number)로 추론
    • 타입 스크립트는 값 / 타입으로 이루어짐
    • 클래스 정의 ( 5.6 ) 🤔🤔🤔
    • 좀 더 살펴보기… 🤔 ★★★★
    • 다형성제네릭 타입의 범위 : 클래스나 인터페이스 전체가 될 수도 있고, 특정 메서드로 한정할 수도 있음
      • 예시
      • class MyMap<K, V> { constructor(initialKey: K, initialKey: V) { ... } get(key: K): V { ... } set(key: K, value: V): void { ... } // 자신만의 제네릭 추가 선언 ( K1, V1 ) merge<K1, V1>(map: MyMap<K1, V1>): MyMap<K| K1, V| V1> { ... } // 정적 메서드 static 에서는 값 수준에서 접근할 수 없어 자신만의 K, V 선언 static of<K, V>(k: K, v: V): myMap<K, V> { ... } }
      인터페이스에서도 제네릭 사용 가능
      • 예시
      • interface MyMap<K, V> { get(key: K): V set(key K, value: V): void }
      함수와 마찬가지로 제네릭에 구체 타입을 명시하거나, 타입 스크립트가 추론하도록 할 수 있음
      • 예시
      • let a = new MyMap<string, number>('k', 1) // MyMap<string, number> let b = new MyMap('k', true) // MyMap<string, boolean> a.get('k') b.set('k', false)
    • 클래스와 인터페이스도 기본값, 상한/하한 설정을 포함한 다양한 제네릭 타입 매개변수 기능 지원
    • 믹스인 🤔🤔🤔둘 이상의 클래스를 상속받는 다중 상속과 관련된 기능 제공상태를 가질 수 있음 ( 인스턴스 프로퍼티 )생성자를 가질 수 있음 ( 클래스가 혼합된 순서와 같은 순서로 호출 )
    • 구체 메서드만 제공 ( 추상 메서드는 안됨 )
    • 역할 지향 프로그래밍(role-oriented programming)
    • trait, mixing 키워드를 제공하지는 않지만, 손쉽게 구현 가능
    • 데코레이터 🤔클래스, 클래스 메서드, 프로펕, 메서드 매개변수를 활용한 메타 프로그래밍에 깔끔한 문법 제공
      @serializable
      class APIPlayload {
      	getValue(): Payload { ... }
      }
      
      데코레이터를 사용하지 않고, 같은 기능을 구현할 수 있음데코레이터 타입 각각에 대해 주어진 이름 범위에 존재하는 함수와 해당 데코레이터 타입에 요구되는 시그니처를 필요로 함직접 구현 / NPM으로 설치기능이 더 완벽해지기 전까지는 일반 함수 사용할 것 권장
    • 특정 시그니처를 만족하는 일반 함수
    • 기본으로 제공하는 데코레이터 X
    • let APIPayload = serializable(class APIPayload { getValue(): Payload { ... } })
    • 장식하는 대상의 함수를 호출하는 기능 제공
    • 타입스크립트의 실험적 기능
    • final 클래스
      1. 생성자를 private로 선언하면, new로 인스턴스 생성/ 클래스 확장 불가능해짐
      • 클래스를 인스턴스화 하는 기능도 사라짐
      • 예시위와 같이 생성자를 private로 선언했다고 하면,생성자가 private이기에 클래스를 확장할 수 없음생성자가 private 이므로 클래스 내부 선언에서만 접근 가능
      • new MessageQueue([]) // ERROR
      • class BadQueue extends MessageQueue { } // ERROR
      • class MessageQueue { private constructor(private message: string[]) { } }
      1. final 클래스처럼 상속만 막고, 인스턴스는 정상적으로 만들 수 있게 하는 방법
      • 예시이렇게 구현을 하면, 인스턴스 생성은 가능
        MessageQueue.create([])
        
      • class BadQueue extends MessageQueue { } // Error
      • class MessageQueue { private constructor(private message: string[]) { } static create(message: string[]) { return new MessageQueue(message) } }
    • 비공개 생성자(private constructor)로 흉내낼 수 있음
    • 디자인 패턴
728x90
반응형
Comments