ActionScript3 中的元编程 Metaprogramming in ActionScript3

今天的ActionScript3看上去已经越来越象JAVA了,这是Adobe刻意营造出来局面,以促进AS快速普及。然而ActionScript在骨子里始终是一个不折不扣的prototype-based的脚本语言,因此AS脚本的本质上是Lambda。在许多环境下,巧妙地利用AS的动态特征,可以通过元编程来减少代码数量并提高实现的质量。这里我举几个例子:

PS: Ruby已经深深地格式化了我对编程语言的理解,因此在以下的实现中我直接参考了Ruby的对应实现。

1. Method missing实现

AS3 提供 flash.util.Porxy 来替代 AS中的Object.__resolve属性。通过继承Proxy类,并重载 callProperty方法,就可以在FLASH的运行时捕捉没有被实现的方法调用。在一些情况下,巧妙得利用这种机制可以大幅降低程序的实现代码量,提高程序的可维护性。比如在开发C/S结构的应用程序时,使用这个方法,可以让服务器端和客户端开发小组在工作上相互解耦,详细请见我在Adobe技术峰会上的发言。

下面就是一段应用在生成环境下的实现代码:

package
{
	use namespace flash_proxy;

        import flash.util.Porxy;
        import mygame.Protocol;
        import mygame.BuffWriter;

	public dynamic class Server
		extends Proxy
	{

		/**
		 * 为Server提供 method missing 捕捉机制,这样向server调用任意未实现的方法都会被编译成指令数据
		 * 发给服务器。
		 * @param name 调用的方法名,即通信协议名,采用驼峰格式,例如 server.runTo
		 * @param rest 发送给服务器的数据参数,比如 server.runTo([40,50])
		 * @return 编译好的发给服务器的bytearray数据
		 */
		flash_proxy override function callProperty(name:*, ...rest):*
		{
			var command:String = (name as QName).localName; // 获取调用的方法名,例如 "runTo"

			// translate camelCase function name to UPPERCASE command name, it become "RUN_TO"
			command = command.replace(/[A-Z]/g, "_$&").toUpperCase();

			// 检查这个调用是否是合法的Protocal命令
			if(!Protocol["hasOwnProperty"]("CMD_"+command))
			{
				// process invalid command
				throw(new ArgumentError("Unknow server command:"+command));
				return;
			}

			var buf:ByteArray = BuffWriter.toBuff(
				Protocol["CMD_"+command],
				rest);

			return buf;
		}
	}
}

2. Duck typing 实现

Duck typing 是Ruby和Python中的一大亮点。Duck typing 本质上是一种结果导向的编程思路,这有悖于OO的结构导向思路,但确是一个非常实用的技巧。AS3引入了类型数组——Vector类型,Vector和Array共享大量的方法和属性,不同之处仅在于Vector是静态类型,而Array是动态类型。在程序中,我们经常会对Array对象进行方法扩展,使用 Duck typing 之后,就可以将这些扩展应用到Vector类型的数据上去。

/**
 * 将一个路径列表序列化并发送给服务器
 * @param path 路径列表,支持 Array, Vector.<Number|int|uint|Point|String>
 */
public function writePath(path:*):void
{
   if(!isArrayLike(path)) return; // 不是可操作的类数组类型
   var parthString:String = ""
   for(var i:int = 0; i<path.length; i++)
	parthString += path[i];
   // ...
   // writing path data to a socket byte array
   // ...
}

/**
 * Finds out if an object is a generic Vector.
 * It works because the value returned for getQualifiedClassName(a vector)
 * is "__AS3__.vec::Vector.<the vector's type>".
 * @param object Object Any object.
 * @return Boolean True if the object is a generic Vector, false otherwise.
 */
function isArrayLike(object:Object):Boolean
{
    if(object is Array) return true;
    var class_name:String = getQualifiedClassName(object);
    return class_name.indexOf("__AS3__.vec::Vector.") === 0;
}

Post a Comment

You must be logged in to post a comment.