Typesafe Enum Pattern in AS3

ActionScript, Design Patterns, Flash, Flex Add comments

I was trying to create a typesafe enum today to hold a Debit and Credit value and realized it could not that easily be done in AS3. In AS2 or in Java (prior to Java 5 when there was no enum construct) you could do the trick by making the constructor of the enum private and instantiate the values as static constants. (See this blog post for an AS2 example.)


Unfortunately there is no such thing as a private constructor in AS3, so we need to find another way of restricting values. First thing I thought of was to use the internal class "hack" used to create singletons to ensure that instantiation can only happen internally in the enum class. However that does not work (probably since the internal class is not yet loaded when trying to instantiate the values). Here's an example.

Actionscript:
  1. package com.domain {
  2.   class DebitCreditEnum {
  3.     public static const DEBIT:DebitCreditEnum = new DebitCreditEnum("debit", new EnumRestrictor());
  4.     public static const CREDIT:DebitCreditEnum = new DebitCreditEnum("credit", new EnumRestrictor());
  5.    
  6.     private var _name:String;
  7.  
  8.     public function DebitCreditEnum(name:String, restrictor:EnumRestrictor) {
  9.       Assert.notNull(restrictor, "The 'restrictor' argument must not be null"); //*
  10.       _name = name;
  11.     }
  12.   }
  13. }
  14. internal class EnumRestrictor {}

(* the Assert class is part of the Prana Framework)

The example above throws the following error:

TypeError: Error #1115: private::EnumRestrictor is not a constructor.
at com.domain::DebitCreditEnum$cinit()
at [newclass]
at global$init()

Apparently, it is impossible to call the constructor of the inner class in a static member of the top class. This also means that we can't create eagerly loaded singletons with the inner class approach. I can imagine that this makes sense in a way, since the inner class will probably be loaded after the top class has loaded and its static members have been initialized. However, I'm not sure about this, so if anyone knows feel free to comment on this.

Static code block to the rescue

AS3 introduces the concept of a static code block. The code inside this block will be executed when the class is loaded in memory. This happens before a constructor call and after all static members have been set. Using a static code block, we can restrict access to the constructor by setting a flag after we have defined the values for our enum. Here's the example:

Actionscript:
  1. package com.domain {
  2.   class DebitCreditEnum {
  3.     public static const DEBIT:DebitCreditEnum = new DebitCreditEnum("debit");
  4.     public static const CREDIT:DebitCreditEnum = new DebitCreditEnum("credit");
  5.    
  6.     private static var _enumCreated:Boolean = false;
  7.  
  8.     // magic happens here, the static code block
  9.     {
  10.       _enumCreated = true;
  11.     }
  12.  
  13.     private var _name:String;
  14.  
  15.     public function DebitCreditEnum(name:String) {
  16.       if (_enumCreated)
  17.         throw new Error("The enum is already created.");
  18.       _name = name;
  19.     }
  20.   }
  21. }


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 

5 Responses to “Typesafe Enum Pattern in AS3”

  1. chris Says:

    Thanks for the tip!

    I too was trying the initial approach, which led me to your post.

  2. Ciprian Says:

    I had a similar problem but I found a work-around. It’s not the nicest one, but you could instantiate your DEBIT and CREDIT in the constructor of the DebitCreditEnum class. You put them as vars, you only provide getters (to make them constant to the outside), you set them as null in the beginning, and in the constructor you test if they are null in wich case you instantiate them.

  3. Josh McDonald Says:

    An interesting technique, but it does rely on the compiler not one day being optimised in a way that the static code blocks happen before static initialisations. The other question is of course since you’re going to be using typesafe checks against the public consts (and DebitCreditEnum is not final), what benefit do you really get by this code? Who cares if somebody calls new DebitCreditEnum() a few times? It can’t break anything relying on this enum class as far as I can see.

  4. David Says:

    I recently came across the problem of neededing some enumerations in code I was writing. I tried to come up with an enumerated type as close to the ones used in c# as I could. I saw that an enum type was simply a collection of string values each basically creates as a static member of the enum type. I came up with a base enum class that then can be exteneded into any enum you wish.

    package Enums.baseEnum
    {

    /** the base class for an enumerated type. The values for the type are determined by a string array that is passed in. A user can get/set the current
    * value and the current index. Passing an invalid index or and invalid value to the setters will result in an argument error. To create a more standard
    * enum type, simply extend this class, creating as many public static constants as you wish, then make an array of all the same constants, and pass to
    * the super constructor of this class. For ease of use, remembering intellisence will alphabetize your enum lists, so keep your strings alphabetized
    * or include the index as part of the static members name**/
    public class Enum
    {
    public function Enum(values:Array, initialValue:String)
    {
    if (values == null)
    _values = new Array();
    else
    _values = values;
    this.value = defaultValue;
    }

    protected var _values:Array;

    private var _index:int;

    public function get index():int
    {
    return _index;
    }
    public function set index(val:int):void
    {
    if(val = _values.length)
    throw new ArgumentError(“Enum set index : supplied value of ” + val + ” is outside current range of 0 – ” + _values.length – 1 + “, determined by this Enum’s array [" + _values.toString()"]“);
    _index = value;
    }

    public function set value(value:String):void
    {
    _index = _values.indexOf(value);
    if(_index == -1)
    throw new ArgumentError(“Enum set value : supplied value of ” + value + ” is not a recognized entry, available values are ” + _values.toString());
    }
    public function get value():String
    {
    return _values[_index].toString();
    }
    }
    }

    this class requires an array passed to it. In common use, i believe an array of strings should suffice, however, I am sure objects could also be passed if needed. The next step is to extend the Enum class with whatever enumerated type you would like, creating static strings for each possible value, and then passing that array in the constructor … as follows

    package Enums
    {
    import Enums.baseEnum.Enum;

    public class Colors extends Enum
    {
    public static const Black:String = ‘Black’;
    public static const Blue:String = ‘Blue’;
    public static const Green:String = ‘Green’;
    public static const Red:String = ‘Red’;

    public function EquipmentType(initialValue:String)
    {
    super(['Black','Blue','Green','Red'],initialValue);
    }
    }
    }

    then in use of this program, one can create as many Colors objects they want, and each one may be set using the set value call and the ClassName. Static variable…. i.e.

    private var myColor:Colors = new Colors(Colors.Blue);

    use myColor.value to get the set value or myColor.value = Colors.XXXXX to set the value.

    What does everyone think?

  5. DamionMurray Says:

    @JoshMcDonald: Once the class is declared as final this implementation of
    enumerated types does prove quite useful. The reason the author prevents
    further instantiations of the class is to ensure strict adherence to the very
    definition of an enumerated type; a type with a fixed number of predefined values.
    This “strictness” may seem unnecessary but consider the following scenario. A
    method takes a DebitCreditEnum instance as a parameter. Inside the method body the
    enum parameter is tested for equality with static constants of the enum class.
    Now, if instantiation of an enum were possible, the method could be passed a DebitCreditEnum instance independent of those declared as static constants.
    In that case, the test in the method body would fail, but this error would crop
    up at run-time and be more difficult to debug compared to a compile-time error.

Leave a Reply

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS Log in