2011-06-15

動態建立物件

今天在噗浪上看到卡卡米問了一個問題使用 Class 類別動態建立物件的建構式參數。 

如果要用 AS3 動態建立物件其實很簡單:

public function createInstance(theClass:Class):* {
  return new theClass();
}



但是如果要建立的物件的類別建構子是有參數的話,要怎麼辦呢?

可以試著修改上面的 function:

public function createInstance(theClass:Class, args:Array):* {
  return new theClass(args);
}

執行後就會發現,很抱歉,參數的數量不符合。

之前介紹過的 Parsley,她有一個 spicelib 的 Library ,裡面的 reflect package 裡有個 Constructor 可以用來動態建立有參數的類別。

用法如下:

var ci:ClassInfo = ClassInfo.forClass(Bar);
var con:Constructor = ci.getConstructor();
var obj:Object = con.newInstance(["hello"]);

這樣就可以很方便的達成卡卡米想要的結果。

由於我很好奇她是怎麼做到的,所以就打開來看了一下 spicelib 的原始檔,結果發現是這樣寫的:

public static function createNewInstance (type:Class, params:Array) : Object {
 switch (params.length) {
  // Now this is really stupid. But there is no "Class.createInstance(args)" in AS3
  case 0: return new type();
  case 1: return new type(params[0]);
  case 2: return new type(params[0], params[1]);
  case 3: return new type(params[0], params[1], params[2]);
  case 4: return new type(params[0], params[1], params[2], params[3]);
  case 5: return new type(params[0], params[1], params[2], params[3], params[4]);
  case 6: return new type(params[0], params[1], params[2], params[3], params[4], params[5]);
  case 7: return new type(params[0], params[1], params[2], params[3], params[4], params[5], params[6]);
  case 8: return new type(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7]);
  default: throw new IllegalArgumentError("Unsupported number of Constructor args: " + params.length);
 }
}

就如同這篇所提到的一樣,AS3並沒有方法可以動態傳遞參數給類別的建構子,所以只能用上面的笨方法。

話說回來,動態建立物件時必須要指定類別,既然已經知道要建立何種類別了,似乎沒有必要透過這麼「搞剛」的方式。

我覺得比較有用的應該是透過類別名稱的字串來建立,例如需要建立的物件是透過外部的檔案(如 XML)來指定的。

這時候就可以利用 flash.utils.getDefinitionByName 來完成:

public function createInstance(className:String, ...args):* {
  // className 必須包含完整 package 名稱,例如 flash.display.Sprite
  var classRef:Class = getDefinitionByName(className) as Class;
  switch(args.length) {
    case 0: return new classRef();
    // ... (略)
    default: throw new IllegalArgumentError("Unsupported number of Constructor args: " + args.length);
  }
}

然後這樣使用:

// 建立 foo package 底下的 Bar 類別
var obj:Bar = createInstance("foo.Bar");

但是假如是用這樣的方式的話:

var obj:Object = createInstance("foo.Bar");

執行時就會出現 ReferenceError: Error #1065: 變數 Bar 未定義 的錯誤。原因在於編譯器不曉得要包含這個類別,所以無法動態建立。

解決的方法就是:

// 列出可能會被動態產生的類別
Bar;
Bar2;
var obj:Object = createInstance("foo.Bar");
var obj2:Object = createInstance("foo.Bar2");

沒有留言: