If branching isn’t used, each time this method is called, all of the browser sniffing code must be run again. This can be very inefficient if the method is called often.
A more efficient way is to assign the browser-specific code only once, when the script loads.
That way, once the initialization is complete, each browser only executes the code specific to its implementation of JavaScript.
The ability to dynamically assign code to a function at run- time is one of the reasons that JavaScript is such a flexible and expressive language.
This kind of optimization is easy to understand and makes each of these function calls more efficient.
Branching isn’t always more efficient.
The next example shows a case when branching should be used, as the branch objects are small and the cost of deciding which to use is large.
Example: Creating XHR Objects with Branching
(There is a more advanced version of this in example for factory pattern.)
First determine how many branches you need. Since there are three different types of objects that can be instantiated, you need three branches.
Name each branch by the type of XHR object that it returns:
/* SimpleXhrFactory singleton, step 1. */
var SimpleXhrFactory = (function() {
// The three branches.
var standard = {
createXhrObject: function() {
return new XMLHttpRequest();
} };
var activeXNew = {
createXhrObject: function() {
return new ActiveXObject('Msxml2.XMLHTTP');
}
};
var activeXOld = {
createXhrObject: function() {
return new ActiveXObject('Microsoft.XMLHTTP');
}
};
})();
Each of the three branches contains an object literal with one method, createXhrObject.
This method simply returns a new object that can be used to make an asynchronous request.
The second part to creating a branching singleton is to use the condition to assign one of these branches to the variable.
To do that, test each of the XHR objects until you find one that the given JavaScript environment supports:
/* SimpleXhrFactory singleton, step 2. */
var SimpleXhrFactory = (function() {
// The three branches.
var standard = {
createXhrObject: function() {
return new XMLHttpRequest();
}
};
var activeXNew = {
createXhrObject: function() {
return new ActiveXObject('Msxml2.XMLHTTP');
}
};
var activeXOld = {
createXhrObject: function() {
return new ActiveXObject('Microsoft.XMLHTTP');
}
};
// To assign the branch, try each method; return whatever doesn't fail.
var testObject;
try {
testObject = standard.createXhrObject();
return standard;
// Return this if no error was thrown.
} catch(e) {
try {
testObject = activeXNew.createXhrObject();
return activeXNew; // Return this if no error was thrown.
} catch(e) {
try {
testObject = activeXOld.createXhrObject();
return activeXOld; // Return this if no error was thrown.
} catch(e) {
throw new Error('No XHR object found in this environment.');
}
}
}
})();
This singleton can now be used to instantiate an XHR object.
The programmer that uses this API need only call SimpleXhrFactory.createXhrObject() to get the correct object for the particular run-time environment.
Branching allows all of the feature sniffing code to be exe- cuted only once ever, instead of once for each object that is instantiated.
This is a powerful technique that can be used in any situation where the particular imple- mentation can only be chosen at run-time.
We cover this topic in more depth when we discuss the factory pattern.
Singleton Pattern Usage
When used for namespacing and modularizing your code, the singleton pattern should be used as often as possible.
It is one of the most useful patterns in JavaScript and has its place in almost every project, no matter how large or small.
In quick and simple projects, a singleton can be used simply as a namespace to contain all of your code under a single global variable.
On larger, more complex projects, it can be used to group related code together for easier maintainability later on, or to house data or code in a single well-known location.
In big or complicated projects, it can be used as an optimizing pattern: expensive and rarely used com- ponents can be put into a lazy loading singleton, while environment-specific code can be put into a branching singleton.
JavaScript’s flexibility allows a singleton to be used for many different tasks. We would even go as far as to call it a much more important pattern in this language than in any other. This is mostly because it can be used to create namespaces, reducing the number of global variables.
This is a very important thing in JavaScript, where global variables are more dangerous than in other languages; the fact that code from any number of sources and programmers is often combined in a single page means variables and functions can be very easily overwritten, effectively killing your code.
That a singleton can prevent this makes it a huge asset to any programmer’s toolbox.
Drawbacks of Singleton Pattern
By providing a single point of access, the singleton pattern has the potential to tightly couple modules together.
This is the main complaint leveraged at this pattern, and it is a valid one.
There are times when it is better to create an instantiable class, even if it is only ever instanti- ated once.
It also makes your code harder to unit test because it has the potential to tightly couple classes together.
You can’t independently test a class that calls methods from a single- ton; instead, you must test the class and the singleton together as one unit.
Singletons are best reserved for namespacing and for implementing branching methods.
In these cases, coupling isn’t as much of an issue.
There are times when a more advanced pattern is better suited to the task.
A virtual proxy can be used instead of a lazy loading singleton when you want a little more control over how the class gets instantiated.
A true object factory can be used instead of a branching singleton (although that factory may also be a singleton).
Don’t be afraid to investigate the more specific patterns in this book, and don’t settle on using a singleton just because it is “good enough.”
Make sure that the pattern you choose is right for the job.
Summary
Creating reusable and modular code.
Finding ways to organize and document that code is one of the biggest steps toward accomplishing that goal.
Singletons can help out enormously in that regard.
By putting your code within a singleton, you are going a long way toward creating an API that can be used by others without fear of having their global variables overwritten.
It is the first step to becoming an advanced and responsible JavaScript programmer.
No comments:
Post a Comment