Clone Wars! Which side are you on?
Option 1: Use ObjectUtil.copy
The ObjectUtil.copy isn’t too bad it does ByteArray serialization/deserialization. So you get some nice features like deep copy. However (and here’s the bullet to byte) you have to use either RemoteObject metadata or registerClass actionscript. You can read more over here. So if you’re using a RemoteObject metadata or registerClass then this solution is for you! If not true believer then keep on reading.Option 2a: Create/Implement a clone method
So for this one you would first create an interface and then implement it.public
interface
IClone{
function clone():IClone;
}
public
class
MyCloneClass implements IClone{
public
var
cloneProperty1:String;
public var cloneProperty2:String;
public function clone():IClone{
var
myClone:MyCloneClass
= new MyCloneClass();
myClone.cloneProperty1
= this.cloneProperty1;
myClone.cloneProperty2
= this.cloneProperty2;
}
}
So for this one you might think that it’s not too bad or overly complicated. You’re right not complicated at all. However (and here’s the bullet to byte) this can get unwieldy with large property sets or a large Class collection, you have to implement the interface/method and then provide the needed assistance to pull this out. If you have time for this great! It’s simple to do, simple to read, simple to maintain, works the hell out of KISS (keep it simple stupid!).
Option 2b: Create a generic clone method
So option 2a is nice. But can become time intensive. So here is one variation that is great but not the best OO public
function
clone( cloneObj:Object , instance:Object = null ):Object {
if(
instance !=
null ) {
var
c:Class = cloneObj['constructor'] as Class;
instance
= new c();
}
var
classInfo:XML
= describeType(cloneObj);
//
List the object's variables, their values, and their
types.
for
each ( var v:XML in classInfo..variable ) {
instance[v.@name]
= cloneObj[v.@name];
}
//
List accessors as properties.
for
each ( var a:XML in classInfo..accessor ) {
//
Do not user the property if it can't be read or
written
if(
a.@access == 'readwrite' ) {
instance[
a.@name ] =
cloneObj[a.@name];
}
}
return
instance;
}
I like this option quite a bit. It requires you to create the new instance and pass it in or you can let the function try to create the new object but the class will require a constructor that doesn’t need any parameters passed in. So this function uses describeType to get an understanding of the class information of the passed in object and to map it’s variables and accessors. Notice that the accessors require a ‘readwrite’ access, since we need to read the value from the old object and assign the value to the clone object.
Option 2c: Create a clone method that’s a little from column A and a little from column B
public class ValueObject {
public
function
clone( deep:Boolean = false , instance:ValueObject = null ):ValueObject
{
if(
instance ==
null )
{
var
c:Class = this['constructor'] as Class;
instance
= new c()
as ValueObject;
}
//
get class info from instanstiated object (aka the
'this' pointer)
var
classInfo:XML
= describeType(this);
var
propertyName:String;
var
propertyValue:Object;
//
List the object's variables, their values, and their
types.
for
each ( var v:XML in classInfo..variable )
{
propertyName
= v.@name;
propertyValue
= this[propertyName];
checkObjectTypeAndClone(propertyName
, propertyValue
, deep , instance);
}
//
List accessors as properties.
for
each ( var a:XML in classInfo..accessor )
{
//
Do not user the property if it can't be read or
written
if(
a.@access == 'readwrite' )
{
propertyName
= a.@name;
propertyValue
= this[propertyName];
checkObjectTypeAndClone(propertyName
, propertyValue
, deep , instance);
}
}
return
instance;
}
protected
function
checkObjectTypeAndClone(
propertyName:String
,
propertyValue:Object
, deep:Boolean , instance:Object ):void
{
if(
deep &&
propertyValue is ValueObject )
{
instance[propertyName]
= ValueObject(propertyValue).clone(deep);
}
else
if( propertyValue is Array
)
{
instance[propertyName]
= cloneArray(propertyValue as Array , deep);
}
else
if( propertyValue is ArrayCollection
)
{
instance[propertyName]
= new ArrayCollection(cloneArray(ArrayCollection(propertyValue).source , deep));
}
else
{
instance[propertyName]
=
propertyValue;
}
}
protected
function
cloneArray( val:Array , deep:Boolean = false ):Array
{
var
clonedObj : Array
= new Array;
for(
var i:int
= 0 ; i
< val.length ; i++
)
{
if(
deep &&
val[i] is ValueObject )
{
clonedObj[i]
= ValueObject(val[i]).clone(true);
}
else
if( val[i] is
Array )
{
clonedObj[i]
= cloneArray(val[i]
, deep);
}
else
if( val[i] is
ArrayCollection )
{
clonedObj[i]
= new ArrayCollection(cloneArray(ArrayCollection(val[i]).source , deep));
}
else
{
clonedObj[i]
= val[i];
}
}
return
clonedObj;
}
} This option requires that any object you want cloned extends ValueObject. Also if you notice this function takes into consideration a need for a deep copy of only other ValueObjects and Array/ArrayCollection. Now if you do a deep copy all constructors will need to allow for no parameter pass in. I like this one the most, as it takes from option A and option B and makes it easy but extending the ValueObject, although there is the issue of basic Collection, such as if you wanted to clone an XMLList or clone a dynamic hash array, you would have to build that functionality and testing in. This one trades off not having to write a specific clone implementation for each method with having to design to get to underlying object that might be contained inside a property.