JavaScript Design Patterns
In
today’s blog, we will look at a number of JavaScript design patterns and
implementation of those patterns. There is not an ideal pattern or set of
patterns to use in the web application we work on. Each script and web
application is its own needs and we need to think about where a pattern can offer real value to an
implementation. For example, some projects may benefit from the decoupling
benefits offered by the Observer pattern whilst others may simply be too small
for decoupling to be a concern at all.
So
a firm grasp of design patterns means to identify the problems and integrate
the design patterns in the application to solve those problems more easily.
We
will discuss the following design patterns:
- Constructor Pattern
- Module Pattern
- Revealing Module Pattern
- Singleton Pattern
- Observer Pattern
- Mediator Pattern
- Prototype Pattern
- Command Pattern
- Facade Pattern
- Factory Pattern
- Mixin Pattern
- Decorator Pattern
- Flyweight Pattern
The Constructor Pattern
In object-oriented programming languages, a constructor is a special method used to initialize a newly created object once memory has been allocated for it. In JavaScript, as almost everything is an object, we're most often interested in object constructors.Object constructors are used to create specific types of objects - both preparing the object for use and accepting arguments which a constructor can use to set the values of member properties and methods when the object is first created.
There are three basic way to create new object in JavaScript.
var myObject = {};
var myObject = Object.create( null );
var myObject = new Object();
There are then four ways in which keys and values can then be assigned to an object:
// 1. Dot syntax
myObject.someKey = "Hello World";
var key = myObject.someKey;
// 2. Square bracket syntax
myObject["someKey"] = "Hello World";
var key = newObject["myKey"];
// 3. Object.defineProperty
anOldObject.defineProperty( myObject,
"someKey", {
value: "for more control of the property's
behavior",
writable: true,
enumerable: true,
configurable: true
});
// If the above feels a little
difficult to read, a short-hand could
// be written as follows:
var defineProp = function ( obj,
key, value ){
config.value = value;
Object.defineProperty( obj, key, config
);
};
// To use, we then create a new
empty "person" object
var person = Object.create( null );
// Populate the object with
properties
defineProp( person,
"car", "Delorean" );
defineProp( person, "dateOfBirth",
"1981" );
defineProp( person,
"hasBeard", false );
// 4. Object.definePropertiesSet properties
Object.defineProperties( newObject,
{
"someKey": {
value:
"Hello World",
writable:
true
},
"anotherKey": {
value:
"Foo bar",
writable:
false
}
});
As we all know, JavaScript doesn't
support the concept of classes but it does support special constructor
functions that work with objects.
function Car( model, year, miles )
{
this.model
= model;
this.year
= year;
this.miles
= miles;
this.toString
= function () {
return
this.model + " has done " + this.miles + " miles";
};
}
// We can create new instances of
the car
var civic = new Car( "Honda
Civic", 2009, 20000 );
var mondeo = new Car( "Ford
Mondeo", 2010, 5000 );
console.log( civic.toString() );
console.log( mondeo.toString() );
The Module Pattern
Modules
are an integral piece of any robust application's architecture and typically
help in keeping the units of code for a project both cleanly separated and
organized.
In
JavaScript, there are several options for implementing modules. These include:
- The Module pattern
- Object literal notation
- AMD modules
- CommonJS modules
- ECMAScript Harmony modules
In
JavaScript, the Module pattern is used to further emulate the concept of
classes in such a way that we're able to include methods and variables inside a
single object, thus protecting particular parts from the global scope.
The Module pattern
The
Module pattern encapsulates "privacy", state and organization using
closures. It provides a way of wrapping a mix of public and private methods and
variables, protecting pieces from leaking into the global scope and
accidentally colliding with another developer's interface. With this pattern,
only a public API is returned, keeping everything else within the closure
private.
It
should be noted that there isn't really an explicitly true sense of
"privacy" inside JavaScript because unlike some traditional
languages, it doesn't have access modifiers. Variables can't technically be
declared as being public nor private and so we use function scope to simulate
this concept. Within the Module pattern, variables or methods declared are only
available inside the module itself thanks to closure. Variables or methods
defined within the returning object however are available to everyone.
This
gives us a clean solution for shielding logic doing the heavy lifting whilst
only exposing an interface we wish other parts of our application to use. The
pattern is quite similar to an immediately-invoked functional expression (IIFE)
except that an object is returned rather than a function.
Following is an implementation of the
Module pattern by creating a module which is self-contained.
var myCounterModule = (function () {
var counter = 0;
return {
incrementCounter: function () {
return counter++;
},
resetCounter: function () {
console.log( "counter value prior to
reset: " + counter );
counter = 0;
}
};
})();
// Increment our counter
myCounterModule.incrementCounter();
// Check the counter value and reset
myCounterModule.resetCounter();
In the above example, The counter variable is actually fully protected
from our global scope so it acts just like a private variable.
Now look at a more comprehensive
example of Module pattern.
var myNamespace = (function () {
var myPrivateVar, myPrivateMethod;
// A private counter variable
myPrivateVar = 0;
// A private function which logs any
arguments
myPrivateMethod = function( foo ) {
console.log( foo );
};
return {
// A public variable
myPublicVar: "foo",
// A public function utilizing privates
myPublicFunction: function( bar ) {
// Increment our private counter
myPrivateVar++;
// Call our private method using bar
myPrivateMethod( bar );
}
};
})();
Object Literals
In object
literal notation, an object is described as a set of comma-separated name/value
pairs enclosed in curly braces (
{}
).
Names inside the object may be either strings or identifiers that are followed
by a colon. There should be no comma used after the final name/value pair in
the object as this may result in errors.
var
myObjectLiteral = {
variableKey: variableValue,
functionKey: function () {
// To do something
};
};
Object literals
don't require instantiation using the
new
operator.
Below we can see
a more complete example of a module defined using object literal notation:
var myModule = {
myProperty: "someValue",
// object literals can contain properties and methods.
// e.g we can define a further object for module configuration:
myConfig: {
useCaching: true,
language: "en"
},
// a very basic method
myMethod: function () {
console.log( "My very basic method…!!!" );
},
// output a value based on the current configuration
myMethod2: function () {
console.log( "Caching is:" + ( this.myConfig.useCaching ) ?
"enabled" : "disabled" );
},
// override the current configuration
myMethod3: function( newConfig ) {
if ( typeof newConfig === "object" ) {
this.myConfig = newConfig;
console.log( this.myConfig.language );
}
}
};
// Outputs: My very basic method…!!!
myModule.myMethod();
// Outputs: enabled
myModule.myMethod2();
// Outputs: fr
myModule.myMethod3({
language: "fr",
useCaching: false
});
Using object
literals can assist in encapsulating and organizing our code
Pros and Cons
Pros
Code managability is easy the application using module pattern.This gives us a clean solution for shielding logic doing the heavy lifting whilst only exposing an interface we wish other parts of our application to use.
In many cases the Module pattern is still quite useful and when used correctly, certainly has the potential to improve the structure of our application.
Cons
The disadvantages of the Module pattern are that as we access both public and private members differently, when we wish to change visibility, we actually have to make changes to each place the member was used.We also can't access private members in methods that are added to the object at a later point.
Other disadvantages include the inability to create automated unit tests for private members and additional complexity when bugs require hot fixes
We today's blog, we looked at JavaScript design patterns. In this series of JavaScript design patterns, we will see other design patterns in up-coming blogs.
Happy Programming...!!!
Comments
Post a Comment