Faking Constants in Flash MX 2004 ActionScript 2.0
For a short while I’ve been pondering constants (or complete lack thereof) in ActionScript 2.0 and had a bit of a brain wave at 5′o’clock this morning. Although there is no ‘real’ way to create constants, they can be faked using a little bit of creative classing.
For example, say I have a Car class, and want to set a maximum speed:
class Car { static var MAX_SPEED:Number = 120; // km/h // rest of class ignored }
Download this code: CarClass-1.as
Using traditional class variables, I can modify this value:
Car.MAX_SPEED = 2000; // Is this a SuperThunderbird 10000X
Download this code: CarClassCall-1.as
If I was able to define this value as const or final it wouldn’t be allowed to be changed, and I should get either a compile-time error or it should ignore the assignment. But ActionScript 2.0 doesn’t allow this… or does it?
Well, no, it doesn’t. At least not explicitly, but we can hack together something that comes close.
The first thing you need to do is remove the static variable variable from the public scope. Yes, that’s right, make it private to the class, so that it’s not seen outside:
class Car { private static var MAX_SPEED:Number = 120; // km/h // rest of class ignored }
Download this code: CarClass-2.as
Now, we need to rename it so that the name we’re going to use for accessing the constant is available:
class Car { private static var __MAX_SPEED:Number = 120; // km/h // rest of class ignored }
Download this code: CarClass-3.as
We’ve now made the class variable private and re-named it, but how does this help make it a constant? Well, this is where the much maligned getters and setters come in handy.
Using a public getter called MAX_SPEED (which was the same as the original variable) we can access the internal value of the class variable, without having to worry about its real name:
class Car { private static var __MAX_SPEED:Number = 120; // km/h public static function get MAX_SPEED():Number { return __MAX_SPEED; } // rest of class ignored }
Download this code: CarClass-4.as
But is this really constant? We can use:
trace(Car.MAX_SPEED);
Download this code: CarClassCall-2.as
to retrieve the value, but what happens if we try to set it? Using:
Car.MAX_SPEED = 2000;
Download this code: CarClassCall-3.as
we get this compile-time error:
**Error** Scene=Scene 1, layer=Layer 1, frame=1:Line 1: Type mismatch in
assignment statement: found Number where Function is required.
Urgh, how ugly! We don’t want errors looking like this cropping up if an assignment is made, which we can remove by writing MAX_SPEED’s setter method, but simply omit the assignment code:
class Car { private static var __MAX_SPEED:Number = 120; // km/h public static function get MAX_SPEED():Number { return __MAX_SPEED; } public static function set MAX_SPEED(new_value:Number):Void { // nothing doin'! } // rest of class ignored }
Download this code: CarClass-5.as
OK, so now it works… or does it? We’re not seeing the compile-time error any more but, if you think about it, setting a constant to a new value is a syntax error. The program should (correctly) use the same constant value throughout the lifetime of the execution of the code, but what if this new value was set, and there’s an expectation (by the programmer using our custom class) somewhere down the line for it having changed? That shouldn’t be allowed, should it?
Wouldn’t it be better to display an new error, a “constant can’t be set” error? Yes, I think it would. We’ll need to make one then.
Create another class based on the built-in Error class, although we’re going to customise it to be a CompileAssignmentError (save it as CompileAssignmentError.as):
class ConstantAssignmentError extends Error { var message:String = 'Error: Constants cannot be reassigned! '; function ConstantAssignmentError(obj:Object,value:Object) { this.message += ' ('+obj.toString()+' = '+value.toString()+')'; } }
Download this code: CompileAssignmentError.as
Lovely. The class, although rather simplistic, gives you the gist of what it does. Now we can use it to stop our Car class’s variables from being assigned, and generate an appropriate error:
class Car { private static var __MAX_SPEED:Number = 120; // km/h public static function get MAX_SPEED():Number { return __MAX_SPEED; } public static function set MAX_SPEED(new_value:Number):Void { throw new ConstantAssignmentError('Car.MAX_SPEED', new_value); } // rest of class ignored }
Download this code: CarClass-6.as
Now, trying the assignment again:
Car.MAX_SPEED = 2000;
Download this code: CarClassCall-4.as
we get this is the Output window:
Error: Constants cannot be reassigned! (Car.MAX_SPEED = 2000)
Sweet!
Known Limitations
The Error class is only able to handle run-time errors, not those at compile-time, but by using this method during the development phase we can reduce or eliminate assignments made to variables that should be treated as constants.
Subclassing may expose your private class members, but you can’t do this accidentally, so I’m not going to fret about it too much.
There’s also a larger amount of code involved, including the use of an extra class and two getter/setter methods, where a const or final keyword would have eliminated the need for this altogether, but if you’re distributing your classes then this removes any worry about overwriting your constants, as well as flagging up accidental assignments like so:
if (2000 = Car.MAX_SPEED) { trace("Woo! Fast!"); }
Download this code: CarClassCall-5.as
would generate an error, because surely you meant to type:
if (Car.MAX_SPEED == 2000) {...}
Download this code: CarClassCall-6.as
Hope you found this useful. Any comments gratefully received.