どうしても動的にインスタンスを作りたかったのですが、
そういうのってReflect
クラス使えばいけるだろって思っていたけど、どうも出来そうにない。
そもそもReflect
ってオブジェクトの中身を動的にゴニョゴニョするやつでそういう機能はなくて然るべきですよね。
Typeクラスを使おう
じゃあ、どうするか?
Haxeのapiドキュメントで標準クラスの中身を眺めていたら、Type
クラスというのがあってそれ使えば行けそう。
Type.typeof()
しか使い方知らなかったよ… 色々出来るんですね;
動的にクラスを取得する
Type.resolveClass
を利用します。
Type.resolveClass("path.to.Class");
動的にインスタンスを生成する
Type.createInstance
とType.resolveClass
を利用します。
// 第二引数はコンストラクタに渡す引数です
Type.createInstance(Type.resolceClass("path.to.Class"), []);
利用にあたっての注意点
javascriptをターゲットにしか利用していないのですが、その中でも落とし穴が結構ありそうでした。
resolveClassではHaxeのコードからのみクラスを取得可能
これはどういうことかというと、動的にクラスを取得するラッパーのようなものを作成してjsをターゲットにコンパイル、jsから動的にクラスを作成するラッパーメソッドをコールしてもクラスは作成出来ないということです。
サンプルコードはこんな感じ。
/**
* クラス名を引数にとってクラスの実態を返すType.resolveClassのラッパー
*/
@:expose('DynamicClass')
class DynamicClass {
public static function getClass(className): Class {
return Type.resolveClass(className);
}
static function main() {}
}
// HaxeのコードからgetClassを利用する分には問題なく使える
import DynamicClass;
class Foo {}
class ClassGetter {
public static function get(): Void {
trace( DynamicClass.getClass('Foo') ); // Class Foo !
}
}
// javascript上のコードからgetClassを利用するとクラス?を返さない
function Foo() {}
console.log( DynamicClass.getClass('Foo') ); // null !?
何故か?
Haxeはjavascriptにコンパイルする時に、Haxe内で定義されたクラスを$haxeClasses
という変数に保持するようなコードを出力します。
Type.resolveClass
は$haxeClasses
の中にクラスが保持されているかを見て、クラスを返却しているのです。
js上で定義されたクラス?は$haxeClasses
に保持されないためにjs上から動的なクラス?の取得が出来なくなっています。
そもそも、jsにはクラスの概念が無いので正しい動作なのかもしれませんが;
解決策
かなり無理やりな感じではあるけど、windowオブジェクトから対象のクラス?名をキーにして取得してあげればいい。
var class = Type.resolveClass('Foo');
if (class == null) {
untyped class = Browser.window["Foo"];
}
Type.createInstance(class, []);
javascriptでクラスみたいなのを定義するときは大抵namespaceの中に定義するから、
実際は引数をnamespace含むクラス?名をparseして…っていう処理を作ってあげないといけないと思う。
createInstanceでコンストラクタに与えられる引数は8つまで
Type.createInstance
の第二引数にはコンストラクタに与える引数を設定出来ますが、
jsのコンパイルコードを見る限り最大でも8つまでという制限があるようです。
8つも引数を取る処理を今まで目にしたことが無いのでさほど問題ではありませんが…
なんだかコンパイル後のコードが気持ち悪い、これはパフォーマンス上の都合なのでしょうか?
Type.createInstance = function(cl,args) {
var _g = args.length;
switch(_g) {
case 0:
return new cl();
case 1:
return new cl(args[0]);
case 2:
return new cl(args[0],args[1]);
case 3:
return new cl(args[0],args[1],args[2]);
case 4:
return new cl(args[0],args[1],args[2],args[3]);
case 5:
return new cl(args[0],args[1],args[2],args[3],args[4]);
case 6:
return new cl(args[0],args[1],args[2],args[3],args[4],args[5]);
case 7:
return new cl(args[0],args[1],args[2],args[3],args[4],args[5],args[6]);
case 8:
return new cl(args[0],args[1],args[2],args[3],args[4],args[5],args[6],args[7]);
default:
throw "Too many arguments";
}
return null;
};
まとめ
ご利用は計画的に。。。