TypeScript - 클래스(믹스인, 데코레이터, final, 디자인 패턴)

euNung·2022년 7월 21일
0

TypeScript

목록 보기
5/8

Mixin

: 여러 컴포넌트 간에 공통으로 사용하고 있는 로직, 기능들을 재사용하는 방법
: 동작과 프로퍼티를 클래스로 혼합할 수 있게 해주는 패턴
: 동작을 캡슐화할 뿐만 아니라 동작을 재사용할 수 있도록 도와줌

// 모든 생성자
type ClassConstuctor = new (...args: any[]) => {}

// C는 최소 클래스 생성자
function withEZDebug<C extends ClassConstructor>(Class: C) {
  // C를 상속하는 익명 클래스 생성자 반환
  return class extends Class {
    // constructor에 아무런 로직이 없으면 생략 가능
    constructor(...args: any[]) {
      super(...args)
    }
    
    debug() {
      let Name = this.constructor.name
      let value = this.getDebugValue()
      return `${Name} (${JSON.stringify(value)})`
  }
}
  • 클래스가 getDebugValue 메서드를 반드시 구현하도록 강제하는 방법
// 제네릭 타입 매개변수 추가
type ClassConstuctor<T> = new (...args: any[]) => T

function withEZDebug<C extends ClassConstructor<{getDebugValue(): object}>>(Class: C) {
  return class extends Class {  
    debug() {
      let Name = this.constructor.name
      let value = this.getDebugValue()
      return `${Name} (${JSON.stringify(value)})`
  }
}
  • 사용 예제
class HardToDebugUser {
  constructor(private id: number, private firstName: string, private lastName: string) {
  }
  
  getDebugValue() {
    return {
      id: this.id,
      name: this.firstName + ' ' + this.lastName
    }
  }
}

let User = withEZDebug(HardToDebugUser)
let user = new User(3, 'Emma', 'Gluzman')
user.debug()	// 결과: HardToDebugUser({"id": 2, "name": "Emma Gluzman"})

데코레이터

: 타입스크립트의 실험적 기능
: 장식하는 대상의 함수를 호출하는 기능 제공
: 아직 실험 단계의 이능이므로 TSC 플래그를 따로 설정해야함(experfimentalDecorators: true)
: 타입스크립트가 기본을 제공하는 데코레이터는 없음
=> 모든 데코레이터는 직접 구현하거나 NPM으로 설치해야함

  • 데코레이터 타입 각각에 대해 주어진 이름 범위에 존재하는 함수와 해당 데코레이터 타입에 요구되는 시그니처를 필요로 함

    데코레이터 대상 기대하는 타입 시그니처
    클래스 (Constructor: {new(...any[]) => any}) => any
    메서드 Method (classPrototype: {}, methodName: string, descriptor: PropertyDescriptor) => any
    정적 메서드 (Constructor: {new(...any[]) => any}, methodName: string, descriptor: PropertyDescriptor) => any
    메서드 매개변수 (classPrototype: {}, paramName: string, index: number) => void
    정적 메서드 매개변수 (Constructor: {new(...any[]) => any}, paramName: string, index: number) => void
    프로퍼티 (classPrototype: {}, propertyName: string) => any
    정적 프로퍼티 (Constructor: {new(...any[]) => any}, propertyName: string) => any
    프로퍼티 게터/세터 (classPrototype: {}, propertyName: string, descriptor: PropertyDescriptor) => any
    정적 프로퍼티 게터/세터 (Constructor: {new(...any[]) => any}, propertyName: string, descriptor: PropertyDescriptor) => any

  • 데코레이터 구현 예제

    type ClassConstuctor<T> = new (...args: any[]) => T
    
    function serializable<T extends ClassConstructor<{getValue(): Payload}>>(Constructor: T) {
    	 // 주어진 클래스를 상속한 다음 .serialize 메서드를 추가해서 클래스를 장식
     	return class extends Constructor {  
       		serialize() {
         	return this.getValue().toString()
     	}
    }
     
    // 클래스 데코레이터
    @serializable
    class APIPayload {
    	 getValue(): Payload {
      		 ...
     	}
    }
     
    // 데코레이터의 메서드 실행
    let payload = new APIPayload
    let serialized = payload.serialize()	// Error: 'serialize' 프로퍼ㅣ는 'APIPayload' 타입에 존재하지 않음
    
    // 데코레이터 대신 일반 함수로 실행
    let DecoratedAPIPayload = serializable(APIPayload)
    let paylaod = new DecoratedAPIPayload
    payload.serialize()
    • 데코레이터의 메서드 실행 시 오류 발생 이유
      : 타입스크립트는 데코레이터가 장식하는 대성의 형태를 바꾸지 않는다고 가정
      = 메서드나 프로퍼티를 추가하거나 삭제하지 않았다고 가정

      => 데코레이터 대신 일반 함수 사용 권장


final

: 클래스나 메서드를 확장하거나 오버라이드할 수 없게 만드는 기능
: 타입스크립트는 클래스나 메서드에 final 키워드를 지원하지 않지만 '비공개 생성자(private constructor)'로 fianl 클래스를 흉내낼 수 있음

class MessageQueue {
  private constructor(private messages: string[]) { }
  // 비공개 생성자 사용으로 클래스를 인스턴스화 하지 못하는 문제 해결
  static create(messages: string[]) {
    return new MessageQueue(messages)
  }
}

class BadQueue extends MessageQueue { }		// Error: 'MessageQueue' 클래수를 확장할 수 없음
											// 클래스 생성자가 private으로 설정됨

new MessageQueue([])						// Error: 'MessageQueue' 클래스 생성자가  
											// private이므로 클래스 내부 선언에서만 접근 가능

MessageQueue.create([])						// MessageQueue

디자인 패턴

팩토리 패턴

: 어떤 객체를 만들지를 전적으로 팩토리에 위임

type Shoe = {
  purpose: string
}

class BalletFalt implements Shoe {
  purpose = 'dancing'
}

class Boot implements Shoe {
  purpose = 'woodcutting'
}

class Sneaker implements Shoe {
  purpose = 'walking'
}

let Shoe = {
  // type을 유니온 타입으로 지정하여 유효하지 않은 type을 전달하지 못하도록 방지
  create(type: 'balletFlat' | 'boot' | 'sneaker'): Shoe {
    // switch문을 이용해 누락된 Shoe 타입이 없는지 타입스크립트가 확인 가능
    switch (type) {
      case 'balletFlat': return new BalletFlat
      case 'boot': return new Boot
      case 'sneaker': return new Sneaker
    }
  }
}

Shoe.create('boot')		// Shoe

// create의 타입 시그니처를 수정하여 반환값을 드러내도록 하면 추상화 규칙을 깨버리게 됨
빌더 패턴

: 객체의 생성과 객체 구현 방식을 분리

class RequestBuilder {
	private url: string | null = null
  	private data: object | null = null
  private method: 'get' | 'post' | null = null
  
  	setURL(url: string): this {
      this.url = url
      return this
    }

	setData(data: object): this {
      this.data = data
      return this
    }
	
	send() {
      ...
    }
}

// 호출 순서가 바뀐다면 런타임 예외 발생가능 (ex. send를 먼저 호출)
// 설계 개선이 필요
new ReuqestBuilder()
	.setURL('/users')
	.setMethod('get')
	.setData({ firstName: 'Anna' })
	.send()
profile
프론트엔드 개발자

0개의 댓글