자바스크립트와 타입스크립트는 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 을 제공한다
트레잇은 생성자가 없으며 인스턴스 프로퍼티를 지원하지 않는다
떄문에 여러 트레잇을 쉽게 연결할 수 있고 베이스 클래스와 공유하는 상태에 접근할 때 발생하는 충돌도 쉽게 방지할 수 있다