PubSub / Observer Pattern and Coupling
PubSub (or the observer pattern) is obviously the hottest pattern in client side development, and I would like to take a shot at trying to refine the best practices for using it in a flexible and robust way.
The definition provided in the original Gang of Four book on Design Patterns states:
“Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically”
As in any design pattern, an important part is to keep the application loosely coupled and with high cohesion. Technically, every event listener uses the PubSub pattern. Consider this jQuery DOM listener:
$('div#foo').delegate('a', 'click', function () {
// do your stuff
})
In this example we are observing the state of an object (a DOM element) and whenever it changes, we can update the application based on that state change. What’s not great about this example is there is tight coupling between our main app (the listener) and the DOM. If we would like to add another set of elements that will trigger the same action, we will need to define another subscriber (client) and redefine the updating procedure. (imagine a video player we would like to be able to play/pause using different buttons or even a different input sources). This limits our flexibility, and could lead to quite painful maintainability. The observer pattern is widely used with models and views in MVC applications. In some system, there’s stuff like this as a part of the view:
// ...
onAfterFetch: function(data) {
// do something with data
},
onItemsReady: function(items){
items.each(function(item) {
// do something
});
},
// ...
These are explicit callbacks for events/actions triggered by the model. While this works, it breaks the rule of encapsulation and delegated responsibilities. This type of programming creates tight coupling between the view and the model: not only the view is aware of the model internals (does it have to be fetching? could we be pushing stuff into the model?), the view updates itself upon model actions and not upon model state/content changes. Pretty soon we will need to update our view on many different model “actions” that causes the same state change.
Once again, this works, but leaves a lot of loose ends and makes the code less flexible and harder to maintain. Whenever we want to refactor the model and modify a method that has to do with state, we will need to update all of the subscribers, so not only it promotes tight coupling and weak cohesion, it also ends up being more complicated, since a view is now aware of the internals of a model.
A better approach is to subscribe only to the state of the data, ignoring the internal of the model which caused the data to change.
Consider this example, where our view represents a field called “name” in the model and has to keep it up to date:
var MyView = Backbone.View.extend({
initialize: function () {
this.model.on('change:name', this.render, this)
},
render: function () {
var name = this.model.get('name')
// do something with our new name
}
})
In Backbone, a view observers the model data state, and when it changes it may decide to update itself. Note that is this example, the only 2 things the view knows (or cares) about the model are:
- It has an ‘on’ method where you can subscribe to changes in the model’s state
- It has a ‘get’ method where you can access model properties (fields).
Using this loose coupling technique, the view does not have any insight to the model internals, and only aware of the part of the interface it has to be aware of. This makes it easier for us to later change the model, for instance: use another fetching technique (polling / web sockets / pushing from an external client), or any change to the model internals, without having to modify the clients at all.
I’m giving Backbone as an example for a good practice, but there are many other tools and libraries that promotes good techniques, and you could easily write your own stuff, keeping this in mind.
To sum things up, the fact that we have an event driven system, and we use the observer pattern, doesn’t mean we are doing it right. We need to pay attention we keep our modules encapsulated and that our application stays loosely coupled.