Extend a Singleton
For a special purpose I had to extend a Singleton, to make my system fully flexible. I found complicated solutions that wasn't fully working as intended.
To use PureMVC, you have to extend a Singleton (Facade), which is the entry point of the framework. This is working only if you don't use the Facade class (the super Singleton) before using the subclass, otherwise you get an error, as the super singleton has already been instantiated.
Well this might be a simple solution but not working in my case.
First of all, they are a lot of solutions to correctly "lock" a Singleton, because in AS3, private constructor doesn't exist. Some of them have turn around, that you can solve with a bit more code, but the solution I prefer is instantiate the Singleton out of the constructor and method:
private static var \_instance:Singleton = new Singleton();
So, if I use the super singleton with a Singleton.getInstance(), and then use the subclass with a SingletonExtended.getInstance(), this will throw an error as when the SingletonExtended is called, the Singleton is called as well and has already been instantiated.
The solution I found is compare the super class name with a static class name, basically you can't use the keyword "this" when you declare a variable, but using "super" will return the same result (well that's odd but it is what I saw):
private static var \_className:String = getQualifiedClassName(super);
And then compare it with the super classname in the Singleton constructor:
if (\_instance != null && getQualifiedSuperclassName(this) != \_className) throw new Error
if I trace(_className + " - " + getQualifiedSuperclassName(this)) in the Singleton constructor:
- when I use the Singleton class, I get: "null - Object" - when I use the SingletonExtended class, I get: "Singleton - Singleton", meaning the constructor has been called from a subclass.
Until now, it is working pretty well, I can use:
Singleton.getInstance().singletonValue SingletonExtended.getInstance().singletonValue SingletonExtended.getInstance().singletonExtendedValue
But we're not completely done. We can access to our two Singleton... but wait, "two Singleton", is that possible?
In fact, it is completely possible and it is even a problem. Let's say I have a undefined value in Singleton, like:
private var \_myValueNotInitialized:String;
And then you have a public method that is setting a value to this variable:
public function initializeMyValue():void { \_myValueNotInitialized = "I am initialized"; }
You can now use:
Singleton.getInstance().initializeMyValue(); or SingletonExtended.getInstance().initializeMyValue();
But here is the problem: you still have 2 instances. One in Singleton and the other in SingletonExtended. If you use: Singleton.getInstance().initializeMyValue(); and trace: SingletonExtended.getInstance().myValueNotInitialized you will get null, which is normal.
This doesn't make a lot of sense for a Singleton, does it?
The solution I found is updating the Singleton instance with the SingletonExtended one.
In the Singleton, I'm now able to update the instance:
public function updateInstance(instance:Singleton):void { if (instance != null && instance is Singleton) \_instance = instance; else throw new Error("Incorrect Singleton update"); }
And in the subclass SingletonExtended, I change the getInstance method:
public static function getInstance():SingletonExtended { Singleton.getInstance().updateInstance(\_instance); return \_instance; }
This is doing the job I needed, a fully Extended Singleton with no errors accessing the super or he subclass.
I tried to break it, but if it is possible, I didn't find the hole. And if you have a better solution, I'd be very happy to see it.
You can download the source by clicking here, or see the classes below.
The main class:
package {
import flash.display.Sprite;
/**
* <b>Author:</b> Romuald Quantin - <a href="http://www.soundstep.com/" target="_blank">www.soundstep.com</a><br />
* <b>Class version:</b> 1.0<br />
* <b>Actionscript version:</b> 3.0<br />
* <b>Date:</b> 11-2008<br />
*/
public class Main extends Sprite {
//------------------------------------
// private properties
//------------------------------------
//------------------------------------
// public properties
//------------------------------------
//------------------------------------
// constructor
//------------------------------------
public function Main() {
trace("--- public properties ---");
trace(Singleton.getInstance().singletonValue + " >>> from Singleton");
trace(SingletonExtended.getInstance().singletonValue + " >>> from SingletonExtended");
trace(SingletonExtended.getInstance().singletonExtendedValue + " >>> from SingletonExtended");
trace(Singleton.getInstance().singletonValue + " >>> from Singleton");
trace("--- access to public methods ---");
trace(Singleton.getInstance().publicSingletonMethod() + " >>> from Singleton");
trace(SingletonExtended.getInstance().publicSingletonMethod() + " >>> from SingletonExtended");
trace(SingletonExtended.getInstance().publicSingletonExtendedMethod() + " >>> from SingletonExtended");
trace("--- access to private methods ---");
trace(Singleton.getInstance().accessPrivateSingletonMethod() + " >>> from Singleton");
trace(SingletonExtended.getInstance().accessPrivateSingletonMethod() + " >>> from SingletonExtended");
trace(SingletonExtended.getInstance().accessPrivateSingletonExtendedMethod() + " >>> from SingletonExtended");
trace("--- access to protected and overriden methods ---");
trace(Singleton.getInstance().accessProtectedSingletonMethod() + " >>> from Singleton");
trace(SingletonExtended.getInstance().accessProtectedSingletonMethod() + " >>> from SingletonExtended");
trace(SingletonExtended.getInstance().accessOverridenSingletonExtendedMethod() + " >>> from SingletonExtended");
trace("--- value not initialized ---");
trace("before = ", Singleton.getInstance().myValueNotInitialized);
Singleton.getInstance().initializeMyValue(); // or SingletonExtended.getInstance().initializeMyValue();
trace("after = ", Singleton.getInstance().myValueNotInitialized);
// this will generate errors
// as you can't instantiate twice a Singleton or a sublass of Singleton
//var s1:Singleton = new Singleton();
//var s2:SingletonExtended = new SingletonExtended();
}
//
// PRIVATE, PROTECTED
//________________________________________________________________________________________________
//
// PUBLIC
//________________________________________________________________________________________________
}
}
The Singleton class:
package {
import flash.utils.getQualifiedClassName;
import flash.utils.getQualifiedSuperclassName;
/**
* <b>Author:</b> Romuald Quantin - <a href="http://www.soundstep.com/" target="_blank">www.soundstep.com</a><br />
* <b>Class version:</b> 1.0<br />
* <b>Actionscript version:</b> 3.0<br />
* <b>Date:</b> 11-2008<br />
*/
public class Singleton {
//------------------------------------
// private properties
//------------------------------------
private static var _instance:Singleton = new Singleton();
private static var _className:String = getQualifiedClassName(super);
private var _myValueNotInitialized:String;
//------------------------------------
// public properties
//------------------------------------
public var singletonValue:String = "Singleton value";
//------------------------------------
// constructor
//------------------------------------
public function Singleton() {
if (_instance != null && getQualifiedSuperclassName(this) != _className) throw new Error("Singleton is obviously... Singleton.");
}
//
// PRIVATE, PROTECTED
//________________________________________________________________________________________________
private function privateSingletonMethod():String {
return "privateSingletonMethod";
}
protected function protectedSingletonMethod():String {
return "protectedSingletonMethod";
}
//
// PUBLIC
//________________________________________________________________________________________________
public function initializeMyValue():void {
_myValueNotInitialized = "I am initialized";
}
public function updateInstance(instance:Singleton):void {
if (instance != null && instance is Singleton) _instance = instance;
else throw new Error("Incorrect Singleton update");
}
public static function getInstance():Singleton {
return _instance;
}
public function publicSingletonMethod():String {
return "publicSingletonMethod value";
}
public function accessPrivateSingletonMethod():String {
return privateSingletonMethod();
}
public function accessProtectedSingletonMethod():String {
return protectedSingletonMethod();
}
public function get myValueNotInitialized():String {
return _myValueNotInitialized;
}
}
}
The SingletonExtended class:
package {
/**
* <b>Author:</b> Romuald Quantin - <a href="http://www.soundstep.com/" target="_blank">www.soundstep.com</a><br />
* <b>Class version:</b> 1.0<br />
* <b>Actionscript version:</b> 3.0<br />
* <b>Date:</b> 11-2008<br />
*/
public class SingletonExtended extends Singleton {
//------------------------------------
// private properties
//------------------------------------
private static var _instance:SingletonExtended = new SingletonExtended();
//------------------------------------
// public properties
//------------------------------------
public var singletonExtendedValue:String = "SingletonExtended value";
//------------------------------------
// constructor
//------------------------------------
public function SingletonExtended() {
if (_instance != null) throw new Error("SingletonExtended is obviously also... Singleton.");
}
//
// PRIVATE, PROTECTED
//________________________________________________________________________________________________
private function privateSingletonExtendedMethod():String {
return "privateSingletonExtendedMethod";
}
override protected function protectedSingletonMethod():String {
return "protectedSingletonMethod overriden in SingletonExtended, value in super.protectedSingletonMethod(): " + super.protectedSingletonMethod();
}
//
// PUBLIC
//________________________________________________________________________________________________
public static function getInstance():SingletonExtended {
Singleton.getInstance().updateInstance(_instance);
return _instance;
}
public function publicSingletonExtendedMethod():String {
return "publicSingletonExtendedMethod value";
}
public function accessPrivateSingletonExtendedMethod():String {
return privateSingletonExtendedMethod();
}
public function accessOverridenSingletonExtendedMethod():String {
return protectedSingletonMethod();
}
}
}