Tuesday, February 8, 2011

What is the best way to implement a singleton pattern class in Actionscript 3?

Since AS3 does not allow private constructors, it seems the only way to construct a singleton and guarantee the constructor isn't explicitly created via "new" is to pass a single parameter and check it.

I've heard two recommendations, one is to check the caller and ensure it's the static getInstance(), and the other is to have a private/internal class in the same package namespace.

The private object passed on the constructor seems preferable but it does not look like you can have a private class in the same package. Is this true? And more importantly is it the best way to implement a singleton?

  • The pattern which is used by Cairngorm (which may not be the best) is to throw a runtime exception in the constructor if the constructor is being called a second time. For Example:

    public class Foo {
      private static var instance : Foo;
    
      public Foo() {
        if( instance != null ) { 
          throw new Exception ("Singleton constructor called");
        }
        instance = this;
      }
    
      public static getInstance() : Foo {
        if( instance == null ) {
          instance = new Foo();
        }
        return instance;
      }
    
    }
    
    Iain : "private static Foo instance;" - That's not even ActionScript
    From Adam N
  • You can get a private class like so:

    package some.pack
    {
      public class Foo
      {
        public Foo(f : CheckFoo)
        {
          if (f == null) throw new Exception(...);
        }
      }
    
      static private inst : Foo;
      static public getInstance() : Foo
      {
         if (inst == null)
             inst = new Foo(new CheckFoo());
         return inst;
      }
    }
    
    class CheckFoo
    {
    }
    
    Iain : you're not actually creating a singleton here
    Simon Buchan : Well... yeah. I was thinking about the private class problem, and got the real problem wrong. Fixed now?
    Iain : Foo(CheckFoo f) should be Foo(f:Checkfoo) - damn ActionScript backwards writing!
  • I've been using this for some time, which I believe I originally got from wikipedia of all places.

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

    Here's an interesting summary of the problem, which leads to a similar solution.

    Iain : Up vote because this is correct, unlike the other answers. You can also have "instance" as a getter, which is better for code auto completion in FlashDevelop.
    enobrev : I see what you mean, lain. That's likely why Daniel (from the link) instantiates the instance var in the property declaration. I'll update mine (and +1 to your solution for the getter - nice one!)
    From enobrev
  • A slight adaptation of enobrev's answer is to have instance as a getter. Some would say this is more elegant. Also, enobrev's answer won't enforce a Singleton if you call the constructor before calling getInstance. This may not be perfect, but I have tested this and it works. (There is definitely another good way to do this in the book "Advanced ActionScrpt3 with Design Patterns" too).

    package {
        public class Singleton {
    
        private static var _instance:Singleton;
    
        public function Singleton(enforcer:SingletonEnforcer) {
      if( !enforcer) 
      {
        throw new Error( "Singleton and can only be accessed through Singleton.getInstance()" ); 
      }
        }
    
        public static function get instance():Singleton
     {
      if(!Singleton._instance)
      {
       Singleton._instance = new Singleton(new SingletonEnforcer());
      }
    
      return Singleton._instance;
        }
    }
    
    }
    class SingletonEnforcer{}
    
    Theo : This code var x : * = { }; var multiton1 : Singleton = new Singleton(x); var multiton2 : Singleton = new Singleton(x); ...compiles just fine.
    Iain : oh this isn't quite right then! when i'm not so busy i'll check this through
    Simon Buchan : There is no way to make it always error at compile time, just passing 'null' works too. I would be surprised if that *ran* sucressfully - it would be an AS3 bug.
    Theo : Passing null wouldn't work, it would compile, but the code checks for it and you would get a runtime exception. The point of my code example is that it compiles and gets around the check. To fix this you can change the test to "enforcer is SingletonEnforcer", which checks both for type and for null.
    From Iain

0 comments:

Post a Comment