ミックスインとは
TypeScriptには、mixinといったキーワードはありませんが、ミックスインを自分自身で実装 することができます。それは、多重継承(2つ 以上のクラスを拡張するクラス)をシミュレートする方法です。
ミックスインは、複数の振る舞いやプロパティを 1 つのクラスの中にミックスする(混ぜ合わせる)ことを可能にするパターンです。
ミックスインの特徴
typescriptのミックスインには、タイプ ‘any []’の単一のレストパラメーターを持つコンストラクターが必要です。
なのでコンストラクター関数タイプを継承させる必要があります。
値ではなくタイプ(型)を継承させることに気をつけて下さい。
ミックスイン(関数)の動作は、コンストラクターを取り、コンストラクターを返す関数なので、無名のクラスコンストラクターを返します。
ミックスインを実装
これから書くコードはlogメソッドを2つのクラスで共有させるミックスインのコードです。
※以下コードはまだlogメソッドを実装していません。
//レストパラメーターを持つコンストラクターの型(コンストラクター関数タイプ)
type Constructor = new (...args: any[]) => {}
//ミックスインを宣言
//上記の型を継承する事でTは少なくともクラスコンストラクターでなければばらなくなる
function mixinfunc<T extends Constructor>(baseClass: T) {
//無名のクラスコンストラクターを返す
//引数で渡されたクラス(型)を継承している
return class extends baseClass {
constructor(...args: any[]) {
super(...args) //clのコンストラクター呼出し
}
}
}
上記はミックスインの関数に渡す引数の型をジェネリクス型(T)にしています。
そのジェネリクスの型にコンストラクター関数タイプを継承させています。
要するにミックスイン関数(mixinfunc)の引数(baseClass)は少なくともクラスコンストラクター(クラス)でなければばらなくなります。渡される引数をクラスに限定しています。
そしてその引数で渡されたクラス(型)を無名クラスに継承させて返しています。
こうする事で引数として渡されたクラス(baseClass)のプロパティを用いてlogメソッドを記述していく事ができます。
簡単にまとめると、ミックスイン関数はクラスコンストラクターを受け取り、それを継承したクラスを返します。
返される無名クラスはmixinfunc関数に引数として渡されるクラス(引数baseClass)の取る引数を取る必要があります。しかしmixinfunc関数の引数の型はジェネリックになっておりどのようなクラスが渡されるかが解らないのでどのようなクラスが渡されてもいいようにany型の可変長引数にしておく必要があります。
ただこの無名クラスのコンストラクター内には具体的な処理は何も書かれていないので、省略可能です。
return class extends baseClass {
}
以後は省略したコードを記述します。
logメソッド実装
ミックスインの関数mixinfuncにlogメソッドを実装します。
type Constructor = new (...args: any[]) => {
name: string,
id: number
}
function mixinfunc<T extends Constructor>(baseClass: T) {
return class extends baseClass {
// constructor(...args: any[]) {
// super(...args)
// }
log() {
let cName = this.constructor.name
let name = this.name
let id = this.id
return '(cName:' + cName + ', name:' + name + ', id:' +id + ')'
}
}
}
logメソッドは保持するプロパティを取得し文字列表示で返します。
「name」「id」はbaseClass クラスのインスタンスが保持する値になるので、型宣言のConstructorに「name」「id」を追加します。
それではmixinfunc関数より2つのクラスを生成し、生成されたクラスでlogメソッドを呼び出してみます。
//baseクラス
class Sam1 {
constructor(public name: string, public id: number) { }
}
//baseクラス
class Sam2 {
constructor(public name: string, public id: number) { }
}
//それぞれのbaseクラスを継承した2つのクラス
let Sam1LogClass = mixinfunc(Sam1) // Sam1を継承したSam1LogClassクラス
let Sam2LogClass = mixinfunc(Sam2) // Sam2を継承したSam2LogClassクラス
let Sam1LogIns = new Sam1LogClass('yamada', 10)
let Sam2LogIns = new Sam2LogClass('suzuki', 20)
console.log(Sam1LogIns.log()) //出力 (cName:Sam1, name:yamada, id:10)
console.log(Sam2LogIns.log()) //出力 (cName:Sam2, name:suzuki, id:20)
mixinfunc関数にbaseクラスとなる「Sam1 」「Sam12」クラスを渡し、logメソッドの呼び出せるクラスを生成します。
logメソッドの呼び出せる生成されたクラスのインスタンスを生成し、logメソッドを呼び出しています。
まとめ
mixinfunc関数は、引数に渡したクラスを継承したクラスを返します。
mixinfunc関数を使用すれば、mixinfunc関数の戻り値として定義した(無名)クラスにいろいろなクラスを継承させる事をシュミレートする事ができます。
一つのクラスにいろいろなクラスを継承させたい場合に使用できます。また、引数をジェネリクス型にする事で、引数として渡されるクラスに制限(型)を持たせる事もできます。
※)引数の型に関して、クラスである必要があるので「new (…args: any[])」の箇所は固定になるかと思います。
以下、全てまとめたコードです。
type Constructor = new (...args: any[]) => {
name: string,
id: number
}
function mixinfunc<T extends Constructor>(baseClass: T) {
return class extends baseClass {
// constructor(...args: any[]) {
// super(...args)
// }
log() {
let cName = this.constructor.name
let name = this.name
let id = this.id
return '(cName:' + cName + ', name:' + name + ', id:' +id + ')'
}
}
}
//baseクラス
class Sam1 {
constructor(public name: string, public id: number) { }
}
//baseクラス
class Sam2 {
constructor(public name: string, public id: number) { }
}
//それぞれのbaseクラスを継承した2つのクラス
let Sam1LogClass = mixinfunc(Sam1)
let Sam2LogClass = mixinfunc(Sam2)
let Sam1LogIns = new Sam1LogClass('yamada', 10)
let Sam2LogIns = new Sam2LogClass('suzuki', 20)
console.log(Sam1LogIns.log()) //出力 (cName:Sam1, name:yamada, id:10)
console.log(Sam2LogIns.log()) //出力 (cName:Sam2, name:suzuki, id:20)