October 21, 2006

Singleton Pattern in AS3

AS3 does not support private or protected constructors which makes it harder to implement the singleton pattern. Below are some approaches I've run across on the Internet, problems with them, and what I hope (please tell me if I'm wrong) corrections to get a real singleton pattern working.

First up is an entry by Andrew Trice about Singletons in AS3. His code was:

// faulty example
package {
    public class Singleton {
        private static var singleton : Singleton;
		
        public static function getInstance() : Singleton {
            if ( singleton == null )
                singleton = new Singleton( arguments.callee );
            return singleton;
        }
	
        //NOTE: AS3 does not allow for private or protected constructors
        public function Singleton( caller : Function = null ) {	
            if( caller != Singleton.getInstance )
                throw new Error ("Singleton is a singleton class, use getInstance() instead");
            if ( Singleton.singleton != null )
                throw new Error( "Only one Singleton instance should be instantiated" );	
            //put instantiation code here
        }
    }
}

You can defeat this approach with:

var a:Singleton = new Singleton(Singleton.getInstance);
var b:Singleton = new Singleton(Singleton.getInstance);
// a !== b

The constructor is doing a function reference comparison, but the function being compared to is available to the caller which is why it can be passed in to defeat the test.

I was also pointed at an approach created by Matt Chotin and posted to Flex Coders. This is a direct cut'n'paste so there are some syntax errors.

// faulty example
package whatever {
  public class MySingleton {
    public function MySingleton(singletonEnforcer:MySingletonEnforcer) { }

private static var instance:MySingleton;

pubic function getInstance():MySingleton {
if (instance == null)
instance = new MySingleton(new MySingletonEnforcer());
return instance;
}
}
}

//this is in MySingleton.as but is outside the package block
class MySingletonEnforcer {}

You can defeat this approach with:

var a:MySingleton = new MySingleton(null);
var b:MySingleton = new MySingleton(null);
// a !== b

If you don't know about private classes I wrote up some information. This is just a missing null check in the constructor to make sure that a valid reference was passed in. I like this approach better overall since it has more compile time support. Trying to call "new MySingleton()" gives an "expected 1 argument" compile time error and trying to call "new MySingleton(XXX)" with anything but null will give you a class cast exception. But that is my personal preference.

I'd also recommend that you add final to the class definition. While I'm pretty sure you can't get access to stuff that easily in AS3, it is probably best to guard against subclassing.

If you want to use the first approach, it can be fixed with the introduction of a private method:

package {
    public final class Singleton {
        private static var singleton : Singleton;
		
        public static function getInstance() : Singleton {
            if ( singleton == null )
                singleton = new Singleton( hidden );
            return singleton;
        }
        private static function hidden():void {}	
        //NOTE: AS3 does not allow for private or protected constructors
        public function Singleton( caller : Function = null ) {	
            if( caller != hidden )
                throw new Error ("Singleton is a singleton class, use getInstance() instead");
            if ( Singleton.singleton != null )
                throw new Error( "Only one Singleton instance should be instantiated" );	
            //put instantiation code here
        }
    }
}

The second approach can be fixed with the introduction of a null check:

package whatever {
  public final class MySingleton {
    public function MySingleton(singletonEnforcer:MySingletonEnforcer) {
        if (singletonEnforcer == null) {
            throw new Error ("MySingleton is a singleton class, use getInstance() instead");
        }
    }

private static var instance:MySingleton;

public static function getInstance():MySingleton {
if (instance == null)
instance = new MySingleton(new MySingletonEnforcer());
return instance;
}
}
}

//this is in MySingleton.as but is outside the package block
class MySingletonEnforcer {}

This final example, which I think is best, comes from Daniel Hai via Ted Patrick's JAM. The entry is "Singleton Take 2":


package {
public final class Singleton {
private static var instance:Singleton = new Singleton();

public function Singleton() {
if( instance ) throw new Error( "Singleton and can only be accessed through Singleton.getInstance()" );
}
public static function getInstance():Singleton {
return instance;
}
}
}

I've updated Wikipedia with this last example. Please change it if you notice any problems.

Tags: as3 flex pattern singleton