views:

819

answers:

4

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?

A: 

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;
  }

}
Adam N
"private static Foo instance;" - That's not even ActionScript
Iain
A: 

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
{
}
Simon Buchan
you're not actually creating a singleton here
Iain
Well... yeah. I was thinking about the private class problem, and got the real problem wrong. Fixed now?
Simon Buchan
Foo(CheckFoo f) should be Foo(f:Checkfoo) - damn ActionScript backwards writing!
Iain
+2  A: 

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.

enobrev
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.
Iain
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!)
enobrev
+8  A: 

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{}
Iain
This codevar x : * = { };var multiton1 : Singleton = new Singleton(x);var multiton2 : Singleton = new Singleton(x);...compiles just fine.
Theo
oh this isn't quite right then! when i'm not so busy i'll check this through
Iain
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.
Simon Buchan
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.
Theo