copy

Clone Wars! Which side are you on?

So I was watching a few people talk about cloning in actionscript today. And soon I’ll have to be doing cloning on my own as well. So I thought I’d present the options and my own solution.
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.

|