TIR: Typescript | 클래스와 인터페이스 (6) 클래스 - 믹스인

Lumpen·2023년 2월 2일
0

Typescript

목록 보기
11/17

자바스크립트와 타입스크립트는 trait, mixin 키워드를 제공하지 않지만
손쉽게 직접 구현할 수 있다

두 키워드 모두 둘 이상의 키워드를 상속받는 다중 상속과 관련된 기능을 제공하며
역할 지향 프로그래밍을 제공한다
역할 지향 프로그래밍에서는 Shape 이라고 표현하는 대신
특징과 역할읠 표현한다
네 개의 면을 갖고 있어요, 측정할 수 있어요 등
즉 is-a 관계 대신 can, has-a 관계를 사용한다

믹스인은 동작과 프로퍼티를 클래스로 혼합할 수 있게 해주는 패턴으로 다음 규칙을 따른다

  • 상태를 가질 수 있다
  • 구체 메서드만 제공할 수 있다 (추상 메서드 불가)
  • 생성자를 가질 수 있다 (클래스가 혼합된 순서와 같은 순서로 호출됨)

타입스크립트 클래스의 디버깅 라이브러리를 설계한다고 가정하고
해당 라이브러리를 이용해 라이브러리를 사용하는 모든 클래스의 정보를 출력해서
런타임에 클래스르 검사할 수 있다

다음과 같이 사용

class User {
	// ... 
}

User.debug() // 'User({ "id":3, "name": 'Emma" })'

믹스인은 단순히 클래스 생성자를 인수로 받아 클래스 생성자를 반환하는 함수다

type ClassConstructor = new (...args: any[]) => {} 

/*
  1. 모든 생성자를 표현하는 타입
  타입스크립트는 구조 기반으로 타입을 판단하므로 new 로 만들 수 있는 모든 것을
  생성자라고 규정한다 
  어떤 타입이 올지 알 수 없으므로 any 선언 
  (타입스크립트는 확장성을 고려해 void 나 unknown이 아닌 any 선언)
*/

function withEZDebug<C extends ClassConstructor>(Class: C){
  /* 
  	2. 한 개의 타입 매개변수 C 만 받도록 withEZDebug 믹스인 선언
    extends로 확장했기 때문에 C 는 최소한 클래스 생성자여야 한다
    반환 타입은 C 와 새로운 익명 클래스의 교집합으로 타입스크립트가 추론한다
  */
	return class extends Class {
      	// 3. 믹스인은 생성자를 인수로 받아 생성자를 반환하는 함수이므로 익명 클래스 생성자를 반환
    	constructor(...args: any[]) {
          // 4. 이 생성자는 최소한 전달한 클래스가 받는 인수를 받을 수 있어야 한다
        	super(...args)
          // 5. 해당 익명 클래스는 다른 클래스를 상속받으므로 Class의 생성자를 호출해야 한다
        }
    }
}

일반 자바스크립트 클래스처럼 constructor 에 아무런 로직이 없으면
4, 5 번 코드를 생략할 수 있다

이제 디버깅 코드를 추가하면 다음과 같다

type ClassConstructor = new (...args: any[]) => {} 

function withEZDebug<C extends ClassConstructor>(Class: C){
	return class extends Class {
      	debug () {
        	let Name = this.constructor.name
            let value = this.getDebugValue()
            return Name + `(${JSON.stringify(value)})`
        }
    }
}

디버깅에 사용할 getDebugValue() 메서드를 반드시 구현하도록 강제하려면
제네릭 타입을 이용하여 withEZDebug 로 전달한 클래스가 메서드를 정의하게 한다

type ClassConstructor<T> = new (...args: any[]) => T
// 제네릭 매개변수를 타입 추가

function withEZDebug<C extends ClassConstructor<{ 
	  getDebugValue(): object 
  }>>(Class: C){
	// ...
}
// 형제 타입 C를 ClassConstructor에 연결함으로 withEZDebug 로 전달한
//	생성자가 .getDebugValue() 메서드를 정의하도록 강제함
class DebugUser {
	constructor(
  		private id: number,
        private firstName: string,
        private lastName: string
  	) {}
  	getDebugValue() {
    	return {
        	id: this.id
          	name: `${this.firstName} ${this.lastName}`
        }
    }
}

사용

let User = withEZDebug(DebugUser)
let user = new User(3, 'Emma', 'Gluzman')
user.debug() // 'DebugUser({"id": 3, "name": "Emma Gluzman"})' 으로 평가

필요한 수의 믹스인을 클랫그에 제공함으로 더 풍부한 동작을 제공할 수 있고
타입 안전성도 보장된다
믹스인은 동작을 캡슐화할 뿐만 아니라 재사용할 수 있도록 도와준다

스칼라, PHP, 코틀린, 러스트 등 일부 언어는 믹스인과 비슷한 trait 을 제공한다
트레잇은 생성자가 없으며 인스턴스 프로퍼티를 지원하지 않는다 
떄문에 여러 트레잇을 쉽게 연결할 수 있고 베이스 클래스와 공유하는 상태에 접근할 때 발생하는 충돌도 쉽게 방지할 수 있다
profile
떠돌이 생활을 하는. 실업자는 아니지만, 부랑 생활을 하는

0개의 댓글