If you have a singleton that is expensive to configure, or resource-intensive, it might make more sense to defer instantiation until is needed.
Known as lazy loading, this technique is used most often for singletons that must load large amounts of data.
If you are using a singleton as namespace, a page wrapper, or as a way to group related utility methods, they probably should be instantiated immediately.
These lazy loading singletons differ in that they must be accessed through a static method. Instead of calling Singleton.methodName(), you would call Singleton.getInstance().methodName().
The getInstance method checks to see whether the singleton has been instantiated. If it hasn’t, it is instantiated and returned. If it has, a stored copy is returned instead.
To illustrate how to convert a singleton to a lazy loading singleton, let’s start with our skeleton for a singleton with true private members:
/* Singleton with Private Members, step 3. */
MyNamespace.Singleton = (function() {
// Private members. var privateAttribute1 = false; var privateAttribute2 = [1, 2, 3];
function privateMethod1() {
//…
}
function privateMethod2(args) {
//...
}
return {
// Public members.
publicAttribute1: true,
publicAttribute2: 10,
publicMethod1: function() {
//...
},
publicMethod2: function(args) {
//...
}
};
})();
So far, nothing has changed. The first step is to move all of the code within the singleton into a constructor method:
/* General skeleton for a lazy loading singleton, step 1. */
MyNamespace.Singleton = (function() {
function constructor() {
// All of the normal singleton code goes here.
// Private members.
var privateAttribute1 = false;
var privateAttribute2 = [1, 2, 3];
function privateMethod1() {
//...
}
function privateMethod2(args) {
//...
}
return {
// Public members.
publicAttribute1: true,
publicAttribute2: 10,
publicMethod1: function() {
//...
},
publicMethod2: function(args) {
//…
}
}
} //constructor
})();
This method (constructor) is inaccessible from outside of the closure, which is a good thing.
You want to be in full control of when it gets called.
The public method getInstance is used to implement this control.
To make it publicly accessible, simply put it in an object literal and return it:
/* General skeleton for a lazy loading singleton, step 2. */
MyNamespace.Singleton = (function() {
function constructor() {
// All of the normal singleton code goes here. …
}
return { getInstance: function() {
// Control code goes here.
}
})();
Now you are ready to write the code that controls when the class gets instantiated.
It needs to do two things:
- First, it must know whether the class has been instantiated before.
- Second, it needs to keep track of that instance so it can return it if it has been instantiated.
To do both of these things, use a private attribute and the existing private method constructor:
/* General skeleton for a lazy loading singleton, step 3. */
MyNamespace.Singleton = (function() {
var uniqueInstance; // Private attribute that holds the single instance.
function constructor() {
// All of the normal singleton code goes here. …
}
return {
getInstance: function() {
if(!uniqueInstance) { // Instantiate only if the instance doesn't exist.
uniqueInstance = constructor();
}
return uniqueInstance;
}
}
})();
Once the singleton itself has been converted to a lazy loading singleton, you must also convert all calls made to it.
In this example, you would replace all method calls like this:
MyNamespace.Singleton.publicMethod1();
In their place, we would write method calls like this:
MyNamespace.Singleton.getInstance().publicMethod1();
Part of the downside of a lazy loading singleton is the added complexity.
The code used to create this type of singleton is unintuitive and can be difficult to understand (though good documentation can help).
If you need to create a singleton with deferred instantiation, it’s helpful to leave a comment stating why it was done, so that someone else doesn’t come along and simplify it to just a normal singleton.
It may also be useful to note that long namespaces can be shortened by creating an alias. An alias is nothing more than a variable that holds a reference to a particular object.
In this case, MyNamespace.Singleton could be shortened to MNS:
var MNS = MyNamespace.Singleton;
This creates another global variable, so it might be best to declare it within a page wrapper singleton instead.
When singletons are wrapped in singletons, issues of scope start to arise.
This would be a good place to use fully qualified names (such as GiantCorp.SingletonName) instead of this when accessing other members.
To be Continued: Branching
No comments:
Post a Comment