views:

49

answers:

4

If an AS3 method returns a reference to a complex type, is there any way to make that 'readonly', like how you can have const member functions in C++? An architecture I want to use calls for a class building itself from a passed template object... and really the template object should not be modifiable. I'm currently forced to add call-back enumerators and/or lots of extra accessor methods.

+2  A: 

Flex has an ObjectUtil.clone() method that will make a deep copy. The copy will still by modifiable, but since it's a copy, the changes won't propagate back to the original.

The method is no complicated so if you're not using Flex, just add this to a util class:

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;
}
Sam
Even though this works fine, you are still adding the cost of copying the object.
__dominic
@__dominic, yes, and the extra memory. It achieves the objective though. An alternative would be to create all of your classes with a `ReadOnly` flag that once set makes all the setters throw an error, but that would be a lot of work.
Sam
Your implementation is fine considering AS3 offers no way of implementing const member functions. Throwing errors in the const member methods is not such a bad idea, but it add lots of boilerplate code as you say.
__dominic
+1  A: 

There is no way to do that in AS3, there is Sam's way of doing it, but it still requires copying that object before you return it, depending on the complexity of that object, it can impact the performance.

dmck
+1  A: 

Immutable interfaces are a near-equivillant to const-correctness. Here's an example:

interface CPoint {
    function get x():Number;
    function get y():Number;
}

class Point implements CPoint {
    private var _x:Number;
    private var _y:Number;

    public function get x():Number { return _x; }
    public function get y():Number { return _y; }

    public function set x(val:Number) { _x = val; }
    public function set y(val:Number) { _y = val; }

    public function normalize():void {
        var length:Number = Math.sqrt(_x*_x + _y*_y);
        _x /= length;
        _y /= length;
    }

    public function Point(x:Number, y:Number) {
        _x = x; _y = y;
    }
}

If you return a Point as a CPoint reference, then its fields cannot be altered. You can do an explicit cast to a Point from a CPoint to force access, but you can do the same thing with const casting in C++.

Unfortunately, AS3 doesn't support covariance like it should, so things get unnecessarily difficult for const sub-objects. For example, if you had a Line class that was made up of two points, you might want to say line.start.x = 47; if you have full access to the line, but allow reading of line.start.x through an immutable interface. You could do this if there was covariance, but instead you'll need to add separate get properties for mutable and immutable properties. So, you'd end up instead with line.cstart.x for reads from a CLine. Something like this:

interface CLine {
    function get cstart():CPoint;
    function get cend():CPoint;
}

class Line implements CLine {
    private var _end:Point;
    private var _start:Point;

    public function get cend():CPoint { return _end; }
    public function get cstart():CPoint { return _start; }
    public function get end():Point { return _end; }
    public function get start():Point { return _start; }

    public function Line(x1:Number, y1:Number, x2:Number, y2:Number) {
        _start = new Point(x1, y1);
        _end = new Point(x2, y2);
    }
}
Gunslinger47
A: 

I would create a flash.utils.proxy object. You could create a proxy object that has read only implementation of a child that is passed in.

Here is the documentation for creating a proxy object. http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/utils/Proxy.html

Note: Proxy is pretty damn slow, since you'll be bypassing native object checking, and replacing it with a function call -- which when using a lot will be slow. I would do some simple performance testing first.

note: This is pseudo-code.

use namespace flash_proxy;

dynamic class ReadOnly extends flash.utils.Proxy {

    private var target:Object;

    public function ReadOnly(target:Object) {
        this.target = target;
    }

    flash_proxy function getProperty(name:*):*
        return target[name];
    }

    flash_proxy function setProperty(name:*, value:*):void
        // throw an error or do nothing
    }

}

You could then do:

var readOnly:ReadOnly = new ReadOnly(stage.loaderInfo.parameters);
readOnly.someparameter = 'newvalue';
trace(readOnly.someparameter); // should be old value
Daniel Hai