也谈AS3中的深度复制
深度复制(也叫深克隆)在一门成熟的面向对象语言中已经不是什么新鲜的事情了,在Java和.NET中都有着比较完善的实现方式。而在AS3中似乎没有一个完美的解决方案。网上有些所谓的AS3深度复制的终极方案,但都经不起推敲,反倒不如一些虽然不完善但比较客观的论述来的有说服力。最近几天google了很多关于深度复制的资料,也翻了很多各大论坛的老帖子,得出的结论是AS3并不支持复杂对象的深克隆的相关功能。至于是Adobe有意为之,还是机制上的缺陷,不是本文讨论的范畴。写这篇文章,一是对这几天查阅资料的一个总结,再就是记录下我这几天的一些有意义思考。
说到深克隆,思路无非有两个:一是通过序列化和反序列化;二是通过语言的反射机制,递归遍历对象的各级属性,然后copy之。Java和.NET上最成熟的做法是第一条,序列化和反序列化。而且这两种语言在序列化的时候都保存了类型信息,这样做的好处在于反序列化的时候可以把得到的对象转化成对应的类型。而到了AS3中,情况就比较复杂了。
对于Object类型的对象,直接用ObjectUtil.copy()方法,就可以得到该对象的一个深拷贝。不妨先看看ObjectUtil.copy()方法的源代码:
- public static function copy(value:Object):Object
- {
- var buffer:ByteArray = new ByteArray();
- buffer.writeObject(value);
- buffer.position = 0;
- var result:Object = buffer.readObject();
- return result;
- }
典型的序列化和反序列化的实现。只不过序列化的结果是AMF格式。关于AMF,又是一个很大的话题,这里也不多做讨论。
对于非Object类,用ObjectUtil.copy()方法返回对象,然后将其强制转换为之前的类型,目前只发现了int,uint,Number,Boolean,Array,Date,ArrayCollection这几个类型可以成功转化。规律暂时未找到。而且就算是Array,ArrayCollection这两个类,元素如果不是前面提到的类型,都无法转化为原类型,只能以Object类型存在。
网上还提到了一个注册对象类型的方案,也被某些人称为终极方案。就是在上面那个函数函数体的前面加上注册当前对象类型的语句:
- public static function copy(value:Object):Object
- {
- var typeName:String = getQualifiedClassName(source);//获取全名
- var packageName:String = typeName.split("::")[0];//切出包名
- var type:Class = getDefinitionByName(typeName) as Class;//获取Class
- registerClassAlias(packageName, type);//注册Class
- var buffer:ByteArray = new ByteArray();
- buffer.writeObject(value);
- buffer.position = 0;
- var result:Object = buffer.readObject();
- return result;
- }
很不幸的是这个做法漏洞百出,首先构造器带参数的类不行,再就是对于复杂对象来说,其内部的属性的类往往也需要注册。这就涉及到一个判别属性的递归问题。也就涉及到AS3的反射机制了。如果运气好的话,也许用这种方法你自定义的类可以成功转化,但请记住,这个方法并不是一个通用的成熟的解决方案。
还有一个折中的解决方案,就是依然用ObjectUtil.copy()去处理复杂对象,但得到的结果不去强制转换。这样可以得到一个Object类型,里面的属性和源对象基本是相同的,但会少一些。将他们都用ByteArray.writeObject()写进ByteArray里,会发现ByteArray的长度不一样,复制得到的对象稍微小一些。原理还不清楚,估计和AS3的对象模型有关,我猜是少了原型那一部分。哪位高手知道,请赐教。
综上来说,在AS3中:
- 如果你想拷贝深克隆Array,ArrayCollection,请用ObjectUtil.copy()方法,然后将返回的对象强制转换一下就可以了。当然前提是里面的元素是只能是前面提到的几个类型,不然只能转化为Object类型了,不过数据还在。
- 如果你想用序列化和反序列化的方法完全深克隆一个像Sprite这样的复杂对象,并将其进行正确的类型转化,对不起,不可以,至少在google能找到的网页上,没有人说可以。
- 如果你只是想复制一个复杂对象的数据,完全可以用ObjectUtil.copy()方法就可以了,你会得到一个Object类型的对象,属性名和源对象一样,只不过无法用编辑器的自动完成功能了:)。
- 如果你想深入研究这个问题,请从以下几方面入手:序列化和反序列化,AMF,AS3的反射机制,其他语言的深克隆的实现和机制,AS3的对象模型,AVM2的运行机制等,as3 byte code的格式等。
另外,突然想到一个有意思的问题,假设AS3中有一个实现了深克隆的方法,那么对于两个互相引用的对象,会有什么样的结果呢?
