7
7

More than 5 years have passed since last update.

Haxeでインスタンスを動的に生成する

Last updated at Posted at 2014-05-21

どうしても動的にインスタンスを作りたかったのですが、
そういうのってReflectクラス使えばいけるだろって思っていたけど、どうも出来そうにない。
そもそもReflectってオブジェクトの中身を動的にゴニョゴニョするやつでそういう機能はなくて然るべきですよね。

Typeクラスを使おう

じゃあ、どうするか?
Haxeのapiドキュメントで標準クラスの中身を眺めていたら、Typeクラスというのがあってそれ使えば行けそう。
Type.typeof()しか使い方知らなかったよ… 色々出来るんですね;

動的にクラスを取得する

Type.resolveClassを利用します。

Type.resolveClass("path.to.Class");

動的にインスタンスを生成する

Type.createInstanceType.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;
};

まとめ

ご利用は計画的に。。。

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7