Wednesday, May 25, 2011

javascript design patterns - singleton

Design patterns are not language specific(Or i should say not OOP language specific). They apply to all OOP languages.

Javascript is a plasticine language, it can mimic OOP, and design patterns apply too.

When we use the object literal to create an object, we are actually getting a singleton.

//get a connection instance, a singleton
var connection = {
    type : 'sqlite',
    db: 'test',
    user: 'user',
    pwd : 'pwd'
};

In JavaScript, objects are never equal unless they are the same object, so even if you create an identical object with the exact same members, it won't be the same as the first one:

var conn = {
    type : 'sqlite',
    db: 'test',
    user: 'user',
    pwd : 'pwd'
};
//false
console.log(conn === connection);
//false
console.log(conn == connection);

Well, but that seems not that kindof singleton we are familiar with when programming with PHP or Java, doesn't it?

Singleton means we should have only one instance of a specific resource anytime, anywhere in the application scope. Database connection is a typical example of singleton. In PHP, you probably can only get a db resource in this way: DB::getInstance(). You cannot use 'new' operator to get an instance, and you can't clone this instance either. If you assign the instance to multiple variables, the variables are actually pointing to the same instance, for example:

//$one and $two point to the same instance!
//if somehow you change the status of $one, $two get affected too
$one = DB::getInstance();
$two = DB::getInstance();

Back to our javascript singleton examples. Although both connection and conn are singletons, we actually can have two instances of same resource. I don't think that is the singleton we want to have.

In javascript, we can use object literal to create an object, also we can use 'new' operator(constructor function) to create an object. So probably in a javascript project, we should set the convention that a singleton resource could only be created through 'new' operator, and we ensure the constructor function returns same instance:

//that is what we want to achieve
var connection = new Connection();
var conn       = new Connection();
//should be true
console.log(connection === conn);

So let's see how can we do it in the easiest and simplest way: using function property

var Connection = function() {

    if (typeof Connection.instance === "object") {
        return Connection.instance;
    }
    this.type = 'sqlite';
    this.db   = 'test';
    this.user = 'user';
    this.pwd  = 'pwd';

    Connection.instance = this;
}
var connection = new Connection();
var conn       = new Connection();
//should be true
console.log(connection === conn);

It is clean and easy. But the only drawback is Connection.instance is publicly accessible, so other codes might accidently change it.

To fix the drawback, we can use javascript's self-defining function

var Connection = function() {

    // the cached instance
    var instance = this;

    this.type = 'sqlite';
    this.db   = 'test';
    this.user = 'user';
    this.pwd  = 'pwd';

    // rewrite the constructor
    Connection = function () {
        return instance;
    };
}

This should serve well enough. But, you probably know that there is a drawback by using self-defining function, although that could rarely happen in practice. 

The third solution: using self-invoking function
var Connection;
(function () {
    var instance;
    //constructor
    Connection = function() {
        if (instance) {
            return instance;
        }
        instance = this;
        // all the functionality
        this.type = 'sqlite';
        this.db   = 'test';
        this.user = 'user';
        this.pwd  = 'pwd';
    };
}());

var connection = new Connection();
var conn       = new Connection();

No comments: