-
Notifications
You must be signed in to change notification settings - Fork 4
The Module Pattern
◄ Back (Objects)
Next (Inheritance) ►
The descriptions of this and other patterns comes from Addy Osmani's Learning JavaScript Design Patterns, available online for free.
You've already seen one way to define a module - using object literal notation:
var myEasyModule = {
moduleId: '86492d69-2b86-4098-9739-2a96917a14e4',
identify: function() {
return this.moduleId;
},
config: {
useCaching: true,
language: 'en'
},
getCurrentConfig: function () {
var description = (this.config.useCaching) ? 'enabled' : 'disabled';
console.log('Caching is ' + description);
if (this.config.language === 'en') {
console.log('Language is ' + 'english');
} else {
console.log('Language is ' + this.config.language);
}
},
disableCaching: function () {
this.config.useCaching = false;
}
};
myEasyModule.identify();
myEasyModule.getCurrentConfig();
myEasyModule.disableCaching();
myEasyModule.getCurrentConfig();
This object can function like a basic module, helping us to encapsulate and organize our code. But in order to further emulate class-like functionality in JavaScript, we can use a module pattern designed to include public and private methods and variables inside a single object, shielding these methods and variables from the global or from an enclosign scope. The module pattern encapsulates private state and organization using closures, and it works by returning a public API, usually as an object.
var myImmediatelyInvokedCounterModule = (function () {
var counter = 0;
return {
incrementCounter: function () { return ++counter; },
resetCounter: function () {
console.log('Counter value prior to reset: ' + counter);
counter = 0;
}
};
})();
myImmediatelyInvokedCounterModule.incrementCounter();
myImmediatelyInvokedCounterModule.resetCounter(); // reset from 1
myImmediatelyInvokedCounterModule.incrementCounter();
myImmediatelyInvokedCounterModule.incrementCounter();
myImmediatelyInvokedCounterModule.incrementCounter();
myImmediatelyInvokedCounterModule.resetCounter(); // reset from 3
The version of the module pattern that you will see most often, and that I recommend you use, is the revealing module pattern.
var myRevealingModule = (function () {
var privateVar = "Ben Cherry",
publicVar = "Hey there!";
function privateFunction() {
console.log( "Name:" + privateVar );
}
function publicSetName( strName ) {
privateVar = strName;
}
function publicGetName() {
privateFunction();
}
// Reveal public pointers to
// private functions and properties
return {
setName: publicSetName,
greeting: publicVar,
getName: publicGetName
};
})();
myRevealingModule.setName( "Paul Kinlan" );
I highly recommend using this pattern to encapsulate functionality and keep your code organized and maintainable. At the same time, I also highly recommend that you do not use it to set public and private variables in quite the way that you would in a language like JavaScript. When you create private variables and methods inside of a module, they become almost impossible to test, and the module as a whole becomes harder to reason about. Instead, I prefer to create smaller modules that expose nearly all of their functionality, and which are therefore quite easy to test, but which supply a special convention for identifying private variables and methods (for example prefixing private methods with the $):
var shoppingCart = (function () {
// private variables go up here
var basket = [],
tax = 0;
// Return an object exposed to the public
// I do this right away so that it's easier for another developer
// to come look at this code and understand what it's doing
return {
addItem: addItem,
getItemCount: getItemCount,
getTotal: getTotal,
addTax: addTax,
outputBasket: outputBasket
};
// Add items to our basket
function addItem(values) {
basket.push(values);
}
// Get the count of items in the basket
function getItemCount() {
return basket.length;
}
// Get the total value of items in the basket
function getTotal() {
var index = this.getItemCount(),
total = 0;
while (index--) { total += basket[index].price; }
return total;
}
})();