Go Func’ yourselves

Hah, that’s a catchy title eh… Ok, going right to the topic of this post. Every now and then we come across a requirement to have something like an observer pattern wherein, multiple listeners (observers), want to know about certain event happening on a particular subject. It is a well practised pattern in .NET, but it can be implemented just as easily in Javascript. Although the functional nature of Javascript there are essentially 2 fundamentally different ways to get a observer pattern implemented.

  • The classical way like in .NET, have a Subject that maintains a list of Observer objects. A Subject can add/remove Observers. Any event happening on Subject is notified to each Observer instance.
  • A func’y way where a Subject holds a bunch of callbacks thats it. Any event happening, and the Subject just executes the callbacks.
Each method has its own pros-cons and the approach really depends on the case at hand. The below snippet shows the first classical approach,
var Subject = function(){
this.observers = {};
Subject.prototype.addObserver = function(observer){
if(this.observers.hasOwnProperty(observer.id)){
return;
}
this.observers[observer.id] = observer;
};
Subject.prototype.removeObserver = function(observer){
delete this.observers[observer.id];
};
Subject.prototype.start = function(){
setTimeout(function(){
_.each(this.observers, function(x){x.execute()});
}.bind(this), 3000);
};
};

var Observer = function(id){
this.id = id;
Observer.prototype.execute = function(){
console.log('Executing observer %s', this.id);
};
};

var o1 = new Observer('O1');
var o2 = new Observer('O2');
var s = new Subject();
s.addObserver(o1);
s.addObserver(o2);
s.start(); // Prints 'Executing observer...' twice once for each observer.
setTimeout(function(){
s.removeObserver(o2);
s.start(); // Prints 'Executing observer...' only once.
}, 5000);

In this approach, we need instances of Observer objects even though the only thing that matters truly is the execute method on them. But with this approach, are able to maintain observer instances with their ids and clean them up as required. Sometimes it is important to not have an observer receive an event multiple times if it was fired only once. Here I have used a unique Observer id to address that issue.

Now, here is the more functional approach,
var Subject = function(){

_.extend(this, new EventEmitter());
Subject.prototype.addObserver = function(callback){
this.on('Execute', callback);
};
Subject.prototype.removeObserver = function(callback){
this.removeListener('Execute', callback);
};
Subject.prototype.start = function(){
setTimeout(function(){
this.emit('Execute');
}.bind(this), 3000);
};
};

function callback1(){
console.log('Received on callback1');
}

function callback2(e){
console.log('Received on callback2');
}

var s = new Subject();
s.addObserver(callback1);
s.addObserver(callback2);
s.start();
setTimeout(function(){
s.removeObserver(callback2);
s.start();
}, 5000);

Here, we do not need a Observer function object at all. All we register is our required callback method. It looks nice and clean and functional way to solve the problem at hand. We do need a way to collect multiple callbacks somewhere, I am using the EventEmitter (node.js module), for the job. Basically it maintains a list of callbacks for a particular event name that will be fired by the Subject. You could do this by implementing your own Event Handler mechanism. Regardless how you do, the problem that would remain is unregistering callbacks. Due to the nature adding/removing listeners in Javascript, you need to pass the exact same callback function for removing, that was passed while adding. A pointer to that function would not work too. So you see, the client code has to somehow maintain these callback functions so that it can pass them to the Subject when the client needs to remove a particular callback. This might be a cumbersome thing to do in certain cases. Barring this problem, I think this method might look attractive to most.

Happy Programming!

Abhang Rane


No comments :

Post a Comment

Leave a Comment...