During the earlier days of the language, JavaScript played a more supporting role in application design. JavaScript was more of an afterthought: a simple way to add some effects to the page or maybe some client-side validation for forms.
The recent “rediscovery” of the language, however, made a huge impact on the way we use JavaScript, and it has become the leading star for many recent applications. Client-side scripting is no longer just about adding simple effects to web pages, but has become a way to implement impressive applications that run in the browser.
But as client-side applications get bigger and bigger, the code that powers them become more and more complex. Parts of the application start getting more dependent on other parts, which introduces spaghetti code filled with strong coupling between items. And the more coupling involved, the harder it is to manage applications: modifying existing functionality becomes a chore, and adding new parts becomes one step short of a nightmare.
Any sufficiently complex application has the risk of getting out of hand unless some sort of modular design was setup from the beginning. Designing your application to be modular isn’t a hard thing to do—although it requires some rethinking if you’re not familiar with the process.
In this post, we’ll examine one way of taming complexity in applications using modules that apply the Hollywood Principle. We’ll be using MooTools here, since it’s my weapon of choice and because MooTools itself provides features that enable us to easily implement modular code. However, you can use the same ideas here with any framework you like, so long as they provide the APIs you’ll need.
So if you’re up for it, let’s take a modular stroll.
Growing Simple Apps
We’ll start by examining an example of a very simple client-side application: an address book. It’s a very simple application consisting of a list of contacts and an area for displaying the contact information. Our example application has the following HTML markup:
<ul id="contacts"> <li data-id="contact-001">Garrick Cheung</li> <li data-id="contact-002">Mark Obcena</li> <li data-id="contact-003">Christoph Pojer</li> <li data-id="contact-004">Jabis Sevon</li> <li data-id="contact-005">Tim Wienk</li> </ul> <div id="info"> </div>
The application has a very simple requirement set:
- The contacts are displayed in a list,
ul#contacts, which will be populated on the server side. - Each of the list items will be clickable, and clicking a contact will send an
XMLHttpRequestto the server to retrieve the contact information. - The contact information will be in the form of an HTML string, which will be injected into
div#info.
With those requirements, we came up with the following code:
window.addEvent('domready', function(){
var request = new Request({
url: '/contacts',
link: 'cancel',
onSuccess: function(){
$('info').empty().set('html', this.response.text);
}
});
var contacts = $('contacts').getElements('li');
contacts.each(function(contact){
contact.addEvent('click', function(){
var id = this.get('data-id');
request.send({data: {id: id}});
});
});
});
We used an event handler for the list items’ click event to control our application. When a user clicks a contact, we send our request and then update our div. This of course, is example code so it’s a bit “naive” compared to what you’d write in real life. However, it serves well for our example.
After some time though, we thought of adding new features to our application. We realized that we need to add a highlight to the currently selected item on the list, so we’ll need to update our click handler. We also need to add the new notes functionality into our application for displaying contact notes.
First, we update our HTML to add a div for displaying the notes:
<ul id="contacts"> <li data-id="contact-001">Garrick Cheung</li> <li data-id="contact-002">Mark Obcena</li> <li data-id="contact-003">Christoph Pojer</li> <li data-id="contact-004">Jabis Sevon</li> <li data-id="contact-005">Tim Wienk</li> </ul> <div id="info"> </div> <div id="notes"> </div>
Next, we update our application code, adding in highlighting for contact items and displaying the notes:
window.addEvent('domready', function(){
var notesRequest = new Request({
url: '/notes',
link: 'cancel',
onSuccess: function(){
$('notes').empty().set('html', this.response.text);
}
});
var contactRequest = new Request({
url: '/contacts',
link: 'cancel',
onSuccess: function(){
$('info').empty().set('html', this.response.text);
}
});
var contacts = $('contacts').getElements('li');
contacts.each(function(contact){
contact.addEvent('click', function(){
// highlights
contacts.removeClass('selected');
this.addClass('selected');
// requests
var id = this.get('data-id');
contactRequest.send({data: {id: id}});
notesRequest.send({data: {id: id}});
});
});
});
Suddenly, our click handler for the list items has grown more complex. Not only are we using it to fire our request for contact information, but we also cram our highlight code as well as the note requesting functionality into the click handler. And this is just a “simple” application. Imagine working on code like this for a more complex project with more moving parts.
But the worst thing is that this is not just a hypothetical situation: every now and then you’ll see projects coded like this, with components mangled together into one highly coupled mess.
Problematic Code
There are two main problems with this kind of coding:
The first is object coupling. In our code above for example, our Request instances are highly coupled with our event handler, and it is the event handler which sends the requests by calling the send method directly. This is a problem with many complex applications, and you might be writing strongly coupled code without knowing it. The easiest way to check if you have strongly coupled code is to look at your object references: are your functions and event handlers referencing objects other than themselves and calling their methods? If so, then you have strongly coupled code.
Strong coupling makes your code inflexible. If we needed to change request objects, for instance, we’ll have to change all references to our original one. That might sound okay if you just have 2 references, but think about a huge application with more than a hundred. It could get hairy really easily.
The second problem is that there is no separation of concerns. All our functionality is handled by the click event handler, from changing the style of the list to sending the requests. These are separate operations which don’t really depend on each other, and yet we ram all of them into a single function.
Separation of concerns is an important trait of good code. Parts that don’t concern each other should be separated, and each part should only be concerned with the operations that affect it.
In order to make your applications truly manageable, scalable and flexible, you need to stop writing code like the one above and start thinking about decoupling your code and making concerns separate. And the easiest way to do this is to start making your code modular.
Getting Modular
There are many topics involved when you talk about “code modularity,” but the one we’ll concern ourselves here is the concept of modules. A module is a self-contained component whose main purpose is to encapsulate and group related functionality together. A module can be fully independent and work without depending on other modules, but it’s more common to use separate modules together in order to build an application.
There are many ways to implement modules, but the easiest way—and the one we’ll be discussing here—is through objects. In this model, we’ll use objects to group related functionality together by defining module objects that represent a particular component in our application. This same module model is used in many applications, albeit using different names and APIs.
We’ll start by looking at our previous code and dividing it into logical parts which will become separate modules. Looking at the application, we can see that we have three distinct components: the contacts list, the data viewer and the notes viewer.
The contacts list is the first component, and it’s composed of several smaller parts. First we have the actual list element, and then we have the click handler for the list items, and the code for highlighting the list. Turning this to a module object, we’ll end up with something like this:
var list = {
init: function(){
var self = this;
var list = this.list = $('contacts');
var items = this.items = list.getElements('li');
items.each(function(item){
item.addEvent('click', function(){
self.setHighlight(this)
});
});
},
setHighlight: function(el){
this.items.removeClass('selected');
el.addClass('selected');
return this;
}
};
window.addEvent('domready', list.init.bind(list));
As you see, our list module is a simple object that has two methods: init and setHighlight. The init method is our setup method, and its job is to set object properties that we’ll use later, as well as to add the event handlers to our list items. The setHighlight method, on the other hand, is our original item highlighting code, which we now turned into a separate function. You’ll also notice that we hooked up init method as a handler for DOMReady, and this moves the event handling outside the module.
Our next component is the info viewer, which controls div#info where our contact data will be displayed. We’ll also include the actual data fetching code in this component, since fetching and displaying contact data are both related functionality.
var infoViewer = {
init: function(){
this.viewer = $('info');
this.xhr = new Request({
url: '/contacts',
link: 'cancel',
onSuccess: this.displayContact.bind(this)
});
},
requestContact: function(id){
this.xhr.send({data: {id: id}});
return this;
},
displayContact: function(){
this.viewer.empty().set('html', responseText);
return this;
}
};
window.addEvent('domready', infoViewer.init.bind(infoViewer));
Like our list module, our infoViewer module also has an init method, which we also bound to DOMReady, that’s used for setting up variables and other necessary components. We added 2 methods to our module that relate to contact data: a method called requestContact, which is used to send the request to fetch the data, and another method called displayContact, which acts as the success handler for our request and is used to display the data into the div.
Our final component is the notes viewer, which is very similar to our info viewer. Therefore, we could just tweak our infoViewer module a bit to produce the noteViewer module:
var noteViewer = {
init: function(){
this.viewer = $('notes');
this.xhr = new Request({
url: '/notes',
link: 'cancel',
onSuccess: this.displayNote.bind(this)
});
},
requestNote: function(id){
this.xhr.send({data: {id: id}});
return this;
},
displayNote: function(){
this.viewer.empty().set('html', responseText);
return this;
}
};
window.addEvent('domready', noteViewer.init.bind(noteViewer));
And with that, we now have our three modules.
You’ll notice three things about our module objects. First, they all share the same basic structure: setup is done via the init method, which is used as a DOMReady handler and all methods are defined within the object itself. While the use of the init method name is a personal choice, these examples showcase the use of a consistent API and structure among modules, which is important in order to establish the fact that each component belong to a specific class of objects (i.e. module objects).
The second thing is that we defined the modules using simple JavaScript objects rather than using classes. There are two reasons for this: first is to bypass having to define and instantiate a class which we’ll use only once—two steps that could be merged by simply using objects directly; and second is so that you won’t get the idea that modules can only be done using classes.
The last thing to notice about our module objects is that we eliminated our two original problems regarding separation of concerns and coupling. Each part of the application is now separate, and each module only concerns itself with the features related to it. We also have no coupling, because all our modules only reference themselves.
However, the decoupling also presents a new problem. Sure, our modules are now decoupled, but they’re also disconnected. Right now, each module is concerned only with itself and that’s great, but our applications need our modules to somehow connect with each other to trigger operations. The infoViewer and noteViewer modules in particular need to be able to hook-up to the list module, so that their request methods could be called to populate their viewers.
So how are we going to connect these separate modules? The answer lies, curiously, in Hollywood.
Module, You Have A Callback
The name Hollywood Principle comes from a very common expression that anyone who’s ever auditioned or applied for anything would be familiar with: “Don’t call us, we’ll call you.” This six words present a very interesting and useful paradigm for programming—one that’s very useful in our current discussion.
At the core of the Hollywood Principle is the idea of relinquishing control over operations. In our earlier code, for example, our list’s click handler controlled the sending of the requests by explicitly calling the send methods of the request objects. This doesn’t follow the Hollywood Principle because the control of other objects is done by another object.
To apply the Hollywood principle, the control of the operations has to be given up by our click handler. Instead of calling the send methods of the requests for example, the click handler would only inform the objects that an action has happened. It will be up to the request objects to decide whether they want to react to that action, or ignore it completely.
There are many ways to apply the Hollywood principle in code, like the use of message passing to inform objects about a particular action happening. However, the most common way to do it—and the one that we’re going to use here—is using events and callbacks.
If you think about it, events and callbacks are a great example of relinquished control. Take DOM events, for instance: when a certain action is performed on an element, the element simply fires an event. The element doesn’t really care if any other object is listening, and it has no control over what particular objects will react to the event. The decision of what to do when an event is fired depends on objects—if they want to react to the event, they’ll have to explicitly add a callback using a listener.
Events and callbacks is a great way to connect our modules together, while still maintaining modularity. Instead of modules explicitly calling the methods of other modules, they can simply fire events to which other modules can hook up to using callbacks. There are three main approaches to using events and callbacks for our modules, each with their own pros and cons. And we’ll look at them one by one.
“I Call You, You Call Me”
The first approach is using a pattern called Publish-Subscribe, or PubSub for short. With this pattern, modules broadcast events, together with the data related to that event. Other modules can the listen to these events and use the related event data to perform actions.
In order to use this pattern, we have to transform our modules from simple objects to event-producing objects. Thankfully, MooTools provides us with the Events mixin class which we can use to easily transform our objects into event-producers. If we were using classes, using Events is as simple as adding the class to our Implements member, but since we’re using simple objects, we’re going to use an extension function:
// use Object.append (1.3) or $extend (1.2)
var extend = Object.append || window.$extend;
// the list module
var list = {
init: function(){
var self = this;
var list = this.list = $('contacts');
var items = this.items = list.getElements('li');
items.each(function(item){
item.addEvent('click', function(){
self.setHighlight(this)
});
});
},
setHighlight: function(el){
this.items.removeClass('selected');
el.addClass('selected');
return this;
}
};
// turn the list module into an event-producer
extend(list, new Events);
window.addEvent('domready', list.init.bind(list));
Here, we extended our list module with an instance of Events using the extend function. This gives the module the event-related methods from Events, such as addEvent and fireEvent, which are the two functions we’ll use. Applying the same to all our modules, we’ll get something like this:
var extend = Object.append || window.$extend;
// the list module
var list = {
init: function(){
var self = this;
var list = this.list = $('contacts');
var items = this.items = list.getElements('li');
items.each(function(item){
item.addEvent('click', function(){
self.setHighlight(this)
});
});
},
setHighlight: function(el){
this.items.removeClass('selected');
el.addClass('selected');
return this;
}
};
extend(list, new Events);
window.addEvent('domready', list.init.bind(list));
// the info viewer module
var infoViewer = {
init: function(){
this.viewer = $('info');
this.xhr = new Request({
url: '/contacts',
link: 'cancel',
onSuccess: this.displayContact.bind(this)
});
},
requestContact: function(id){
this.xhr.send({data: {id: id}});
return this;
},
displayContact: function(){
this.viewer.empty().set('html', responseText);
return this;
}
};
extend(infoViewer, new Events);
window.addEvent('domready', infoViewer.init.bind(infoViewer));
// the note viewer module
var noteViewer = {
init: function(){
this.viewer = $('notes');
this.xhr = new Request({
url: '/notes',
link: 'cancel',
onSuccess: this.displayNote.bind(this)
});
},
requestNote: function(id){
this.xhr.send({data: {id: id}});
return this;
},
displayNote: function(){
this.viewer.empty().set('html', responseText);
return this;
}
};
extend(noteViewer, new Events);
window.addEvent('domready', noteViewer.init.bind(noteViewer));
Note that making all modules event-producers isn’t really necessary, because not all of them are going to need to fire events and accept callbacks. However, it is better to make them event-producers from the beginning since you’ll never know if you’ll need them to fire events in the future.
Now that all our modules our event-producers, we’ll need to change them a bit so that they’ll be able to make use of events. The first thing we need to do is to determine how our modules connect to each other. We know that the fetching of the contact info and the note data depend on clicking an item in the list. Therefore, our list module needs to broadcast an event that will tell other modules that an item has been clicked. This broadcast should also contain some data regarding which particular item has been clicked.
The click handler for our list module is in the init method, so let’s modify that to look like this:
init: function(){
var self = this;
var list = this.list = $('contacts');
var items = this.items = list.getElements('li');
items.each(function(item){
item.addEvent('click', function(){
self.setHighlight(this)
self.fireEvent('list.click', {id: item.get('data-id')});
});
});
},
The important line here is the self.fireEvent call inside the click handler. This will fire the list.click event in the list together with an object argument that contains the value of the data-id attribute of the clicked list item. The event name list.click is a convention I personally prefer, but you can use any other event name you want.
Since our init method is starting to balloon again, we better start cleaning it up. We can separate the click hander into its own method, and then remove some extra code we no longer need:
var list = {
init: function(){
var list = this.list = $('contacts');
var items = this.items = list.getElements('li');
items.addEvent('click', this.itemClicked.bind(this));
},
itemClicked: function(e){
var item = $(e.target);
this.setHighlight(item);
this.fireEvent('list.click', item.get('data-id'));
},
setHighlight: function(el){
this.items.removeClass('selected');
el.addClass('selected');
return this;
}
};
extend(list, new Events);
window.addEvent('domready', list.init.bind(list));
Our list module, being the one that fires events, is now designated as the publisher. The other modules, which are going to listen to events, are now going to be designated as subscribers. We can hook up our subscribers to listen to these events using the addEvent method of our list module:
var extend = Object.append || window.$extend;
// the list module
var list = {
init: function(){
var list = this.list = $('contacts');
var items = this.items = list.getElements('li');
items.addEvent('click', this.itemClicked.bind(this));
},
itemClicked: function(e){
var item = $(e.target);
this.setHighlight(item);
this.fireEvent('list.click', item.get('data-id'));
},
setHighlight: function(el){
this.items.removeClass('selected');
el.addClass('selected');
return this;
}
};
extend(list, new Events);
window.addEvent('domready', list.init.bind(list));
// the info viewer module
var infoViewer = {
init: function(){
this.viewer = $('info');
this.xhr = new Request({
url: '/contacts',
link: 'cancel',
onSuccess: this.displayContact.bind(this)
});
list.addEvent('list.click', this.requestContact.bind(this));
},
requestContact: function(id){
this.xhr.send({data: {id: id}});
return this;
},
displayContact: function(){
this.viewer.empty().set('html', responseText);
return this;
}
};
extend(infoViewer, new Events);
window.addEvent('domready', infoViewer.init.bind(infoViewer));
// the note viewer module
var noteViewer = {
init: function(){
this.viewer = $('notes');
this.xhr = new Request({
url: '/notes',
link: 'cancel',
onSuccess: this.displayNote.bind(this)
});
list.addEvent('list.click', this.requestNote.bind(this));
},
requestNote: function(id){
this.xhr.send({data: {id: id}});
return this;
},
displayNote: function(){
this.viewer.empty().set('html', responseText);
return this;
}
};
extend(noteViewer, new Events);
window.addEvent('domready', noteViewer.init.bind(noteViewer));
We connected our infoViewer and listViewer methods to list module by attaching event listeners. Now, when an item in the list is clicked, our other modules will be notified of the event, and they’ll be able to act on the event by sending their requests objects to retrieve the data and display it on the interface.
However, you might have noticed one big problem: strong coupling is back. Our infoViewer and listViewer modules reference the list module directly in order to add listeners, and this makes it harder to change the code in the future. If we want to replace our list module with another one, we’ll have to hunt down all references to our original list module and replace them.
So while this simple PubSub pattern is useful and easy to implement, the trade-off of having explicit module references is too big to make it usable. Therefore, we need a better alternative.
“I Call You, You Call Them”
In order to circumvent the strong-coupling between the modules, we can implement another pattern related to PubSub called the Observer pattern. In this pattern, the subscriber modules no longer connect directly to the publisher. Instead, an intermediary object called the observer connects to the publisher and the subscribers connect to the observer.
The key to using the Observer pattern is the observer object. There are many ways to implement an observer object, ranging from simple to complex, and the one we’re going to use here is a very basic one that’s built by subclassing Events.
var Observer = new Class({
Extends: Events,
$publisher: null,
$handlers: {},
initialize: function(publisher){
this.setPublisher(publisher);
},
setPublisher: function(publisher){
this.detach();
this.$publisher = publisher;
this.attach();
},
attach: function(){
var publisher = this.$publisher,
handlers = this.$handlers;
if (!this.$attached && publisher){
for (var event in handlers){
publisher.addEvent(event, handlers[event]);
}
}
this.$attached = true;
return this;
},
detach: function(){
var publisher = this.$publisher,
handlers = this.$handlers;
if (this.$attached && publisher){
for (var event in handlers){
publisher.removeEvent(event, handlers[event]);
}
}
this.$attached = false;
return this;
},
addEvent: function(type, fn){
var self = this,
handlers = this.$handlers;
this.parent(type, fn);
if (!handlers[type]){
var handler = handlers[type] = function(){
self.fireEvent.apply(self, arguments);
};
this.$publisher.addEvent(type, handler);
}
return this;
}
});
As you can see, we subclassed Events and added a few special methods. The setPublisher method allows us to change the publisher on the fly, making it possible for us to switch publisher objects without subscribers knowing about it. The attach and detach method, on the other hand, manage mass adding and removal of internal handlers of the observer. Finally, we overrode the original addEvent method of the Events class to add the necessary logic for adding the internal handlers of our observer.
Instances of the Observer class act like proxies: they listen to an object for events and then fire the same event for the objects listening to them. Let’s modify our original modules code to use the new Observer class:
var extend = Object.append || window.$extend;
// the list module
var list = {
init: function(){
var list = this.list = $('contacts');
var items = this.items = list.getElements('li');
items.addEvent('click', this.itemClicked.bind(this));
},
itemClicked: function(e){
var item = $(e.target);
this.setHighlight(item);
this.fireEvent('list.click', item.get('data-id'));
},
setHighlight: function(el){
this.items.removeClass('selected');
el.addClass('selected');
return this;
}
};
extend(list, new Events);
window.addEvent('domready', list.init.bind(list));
// the list observer
var observer = new Observer(list);
// the info viewer module
var infoViewer = {
init: function(){
this.viewer = $('info');
this.xhr = new Request({
url: '/contacts',
link: 'cancel',
onSuccess: this.displayContact.bind(this)
});
observer.addEvent('list.click', this.requestContact.bind(this));
},
requestContact: function(id){
this.xhr.send({data: {id: id}});
return this;
},
displayContact: function(){
this.viewer.empty().set('html', responseText);
return this;
}
};
extend(infoViewer, new Events);
window.addEvent('domready', infoViewer.init.bind(infoViewer));
// the note viewer module
var noteViewer = {
init: function(){
this.viewer = $('notes');
this.xhr = new Request({
url: '/notes',
link: 'cancel',
onSuccess: this.displayNote.bind(this)
});
observer.addEvent('list.click', this.requestNote.bind(this));
},
requestNote: function(id){
this.xhr.send({data: {id: id}});
return this;
},
displayNote: function(){
this.viewer.empty().set('html', responseText);
return this;
}
};
extend(noteViewer, new Events);
window.addEvent('domready', noteViewer.init.bind(noteViewer));
We changed three lines in our previous PubSub code. First, we declared an new Observer instance called observer, passing in the list module as an argument. Then we changed our infoViewer and noteViewer code to listen to the observer object rather than the item module. What happens now is that every time the item module fires its list.click event, the observer object will also fire the same event with the same arguments to alert the infoViewer and noteViewer modules that an item was clicked.
This might not look like a very big step from the PubSub model, since our two subscriber modules still have a direct reference to an object. But the benefit here is that there’s no longer a strong coupling between the actual modules. Because our observer proxies the events, and we can easily switch the publisher object if we want to—making it easy to change the implementations if needed. Now if we need to change our list module to another module, all we have to do is call observer.setPublisher(anotherModule) and it’s done. Our subscribers don’t even have to know about the switch.
But while decoupling the publisher modules from the subscriber modules is a great benefit, the direct coupling of the subscriber modules with the observer object is still problematic—especially when you consider the Observer pattern in general.
You see, the Observer pattern implements a one-to-many relationship: a single observer will listen to a single publisher and broadcast to multiple subscribers. In our one publisher application example, this seems easy, since we’ll only have one observer which our subscriber modules will have to know about. But if we start adding more publishers, we’ll need to create observers for each of them and our subscribers will need to keep track of more objects. I don’t know about you, but that doesn’t sound flexible to me.
The Observer pattern solves the problem of coupling between modules, but adds its own coupling problem that doesn’t outweigh the initial benefits. But we’re almost there—we just need to make a few adjustments.
“Tim, Could You Tell Djamil and Chris to Call Val?”
The problem with the PubSub and Observer patterns isn’t really the fact that they couple modules and objects. If we were working on a simple application like our example address book app, there would be no big harm in using these two patterns, since we’re only using a direct reference to a single module—or in the case of the Observer pattern, a single observer. In fact, coupling isn’t really something we can totally eradicate, especially when you consider the use of the window and document objects inside objects and classes.
The true problem lies in the number of couplings involved between modules. Imagine an application with several modules, all of them acting as subscribers and publishers. If we use the PubSub pattern, we’ll end up with all modules referencing each other directly. If we use the Observer pattern, we’ll end up with all modules having their own observer and all modules referencing all these observers directly.
As the number of modules grow, the more we’d want to eliminate direct coupling between them, because having all modules bound together is not really better than having a single module that does everything.
So what can we do? Remember that the main limitation with the Observer pattern is that it only implements a one-to-many relationship: one observer for one publisher. But what if the observer implements a many-to-many relationship? We can use a single observer for all our objects, which reduces the number of direct observer-module coupling.
This is in fact another pattern, called Mediator. In this pattern, all modules will talk to each other indirectly using a proxy object called a mediator. The mediator has a many-to-many relationship with modules: all publisher modules will use the mediator to broadcast events, and all subscriber modules will use the mediator to listen to events.
There are many ways to implement a mediator object, but there’s one way that’s already available to us. This is through the use of a global event-producing object, like the window object. Modules will send messages to the window object using fireEvent, and they will listen to the broadcasts using addEvent.
// the list module
var list = {
init: function(){
var list = this.list = $('contacts');
var items = this.items = list.getElements('li');
items.addEvent('click', this.itemClicked.bind(this));
},
itemClicked: function(e){
var item = $(e.target);
this.setHighlight(item);
window.fireEvent('list.click', item.get('data-id'));
},
setHighlight: function(el){
this.items.removeClass('selected');
el.addClass('selected');
return this;
}
};
window.addEvent('domready', list.init.bind(list));
// the info viewer module
var infoViewer = {
init: function(){
this.viewer = $('info');
this.xhr = new Request({
url: '/contacts',
link: 'cancel',
onSuccess: this.displayContact.bind(this)
});
window.addEvent('list.click', this.requestContact.bind(this));
},
requestContact: function(id){
this.xhr.send({data: {id: id}});
return this;
},
displayContact: function(){
this.viewer.empty().set('html', responseText);
return this;
}
};
window.addEvent('domready', infoViewer.init.bind(infoViewer));
// the note viewer module
var noteViewer = {
init: function(){
this.viewer = $('notes');
this.xhr = new Request({
url: '/notes',
link: 'cancel',
onSuccess: this.displayNote.bind(this)
});
window.addEvent('list.click', this.requestNote.bind(this));
},
requestNote: function(id){
this.xhr.send({data: {id: id}});
return this;
},
displayNote: function(){
this.viewer.empty().set('html', responseText);
return this;
}
};
window.addEvent('domready', noteViewer.init.bind(noteViewer));
You’ll notice some changes between this version of our code and the others. The first one is that we removed the extension code for our modules that add the methods from the Events class. Since we’re going to use the window object to talk between modules, there’s no need to make the modules themselves event-producers because the window object is already an event-producer.
The other thing about this code is that we’re referencing the window object directly and calling its addEvent and fireEvent methods directly from our module code. This makes our modules decoupled from each other in the sense that they no longer need to know about the existence of other modules. All their talk is directly done via the mediator, the window object.
Of course, using the window object as the mediator still seems a bit icky, since we still have coupling involved. However, the costs of this coupling between the window object and a module is outweighed by the benefits of having all your modules totally decoupled from each other. You can add as many modules as you’d like that use this Mediator pattern and still get the flexibility you want, because modules are now totally separate from each other. And because the window object is highly available as well as unswappable (have you ever tried changing the window object with your own implementation?), you can be assured that you’ll never really have to worry about using it.
Moodularity
We certainly covered a mile today, didn’t we? I hope that after reading this post, you’ll start thinking about modularity in your code, and I hope that I’ve given you enough starting points to make your code modular. If you’re new to the idea of events, callbacks and modules, it might take some time before the patterns I’ve discussed here will sink in, but I assure you that after using them in your code, you’ll find your applications more manageable and flexible.
Personally, I use a mixture of these patterns in my work, and I’ll share this mixture to you in the near future with some new code and another post. For the moment, make sure you post your comments, questions or suggestions in the reactions area below. And don’t forget to subscribe to the RSS Feed to get updates on next posts or to follow me on Twitter.
And with that, let’s start making Hollywood proud!
Reactions
post your reactionbarryvan
Nice article — a good read! I implemented a mediator system a while ago for a large project that I’m working on. Unfortunately, though, I couldn’t use events proper.
To explain why, let’s imagine that we have a mix-in for the info viewer. Now let’s also imagine that we’ve set the system up so that the users can turn modules (and mix-ins) on or off. Let’s additionally assume that we load these modules asynchronously, so that we only load the code that’s actually necessary. Now, at start-up, we bootstrap the system, then asynchronously load the modules and mix-ins we require.
This is where the problem can occur. You see, our mix-in’s code could arrive before the info viewer’s code. We don’t want to try mixing-in to a non-existent object. Yes, we could just check for its existence, but then we’re running into strongly-coupled territory again. Instead, we need to be able to know about events that have already been fired (messages that have already been sent), and their data.
In my implementation, I term this a “messaging” system, and use method names like
registerandsend. What we need to do is keep track of messages that have already been sent, and, when a new listener registers itself, we can then fire its callback immediately if the message it’s interested in has already been sent.For the specific example we’re dealing with, the original object could
msg.send('infoViewer.ready', this)when it’s ready to go. The mix-in could, when it’s ready,msg.register('infoView.ready', doStuff). This means that we’re not getting into trouble with strongly-coupled objects, but we’re providing the flexibility for code to be executed asynchronously. Even when you’re concatenating your code into a single file, using this pattern means that it’s not necessary to concatenate the original files in a particular order (except having themessagingcode right near the top).Mark Obcena
Barryvan: Thanks, but you beat me to the punch. :P
Adding asynchronous support to modules and preventing race conditions by storing previously fired events is actually one of the things I’ll discuss in my next post. Since that post is already planned, I won’t go into detail here.
Your mediator implementation is interesting, and it’s basically the same with the mediator example in the last section, but with a different naming scheme. I used the default event methods since they’re readily available, while your implementation uses one that reflects a more “message-passing” inspired model. By the way, that decision is nice. :)
The storing of previously fired events (or in your case, messages) and their replay for new listeners is something that’s important when dealing with async code loading. I opted to stay out of the territory for a bit, not only because it’s future material, but also because I wanted to present a simple system that’s easily implementable. And using the
windowobject, together with the readily available MooTools Event API is perhaps the easiest way to do it.shanebo
Keeto, great write up, thanks!
This is such a tough problem. While your last solution decouples more I can’t help but notice how much more difficult the code is to read. I’m left wondering if the tradeoff is worth it?
Another option would be a simpler, possibly hack, solution of creating an overlap object of methods where the click event calls overlap.click and overlap.click calls each module’s related method so the calls are in one place but not within each module’s methods. However the downside to this is coming up with a naming convention for the overlap object and it’s method names.
Mark Obcena
Shanebo: Thanks man. :)
Yeah, it’s a common problem with using external mediators (as well as observers). Because event operations are performed by calling the methods of external mediator objects, the code looks out of place and a bit weird, as though it’s not meant to be there. Unfortunately, we can’t do anything about it if we’re using explicitly external mediators like this one.
I’m glad you brought up the idea of an overlap object, because it’s one of the things I didn’t discuss here due to length concerns. This is actually a technique that’s similar to the one used by the Qt framework using their variation of the Observer pattern called Signals and Slots.
In the Signals and Slots model, the events that a module fires are called signals while the listeners that a module uses to respond to signals are called slots. The system will then have a special
connectfunction to assign a module’s slots to the appropriate signals. Applying a system like this to our example, you’ll useconnect(list, 'itemClicked', infoViewer, 'requestContact');. The system will then be the one to create the necessary code that will connect these two methods directly. Because the connection is no longer happening within the modules themselves, we still retain a decoupled system.I’m not sure how Qt does it internally, but you could see how the idea of an overlap object could be used in this context. We can implement the
connectfunction so that it automatically creates a single overlap object that could be used for multiple slots—just like an Observer. The difference though is that we don’t need to explicitly define the observer object, since it’s done automatically using theconnectfunction—with the additional benefit of decoupling the actual module connection code from the modules.Arieh
Hey. Amazing article!
It’s so important to have high-level design pattern articles on JS. I myself find that the event pattern is one of the things that make mootools so strong - it’s simply a very high level way to allow modules to interact. I like the way you use the window object to mediate events. I myself think there are reasons to use a designated Mediator, especially when you want to start enhancing the behavior. Creating a mediator interface and providing it at construction (DI) would have made this pattern much stronger. The best example I can think for this is that you might want to add more functionality to the Mediator - such as AJAX history (which is what I’ve done with my HistoryManager).
Then again, JS allows us to easily create DI’s via self-executing lambdas
One last thing - I’m still not sure that I agree with you not using Class. My usual thumb-rule is that when I start using mixins I use Class. Another thing is that I usually start by thinking - “how can I implement what I’m doing to make it reusable” instead of “will i use this in the future”. Your example is classic - there are tons of ways to abstract the above pattern to make it smarter and reusable.
Mark Obcena
Arieh: Glad you liked it. :)
I know that my words above could be misinterpreted by some as an advice to not use classes, but that wasn’t my intention. I wouldn’t have implemented
Observeras a class if I disliked them so much. So to stress a point, I don’t advice against the use of classes—I advice against the misuse of classes. Because this is a complex topic, I’ll save the whole discussion to a probable future post.And as a sidenote, I believe that classes—like frameworks and libraries—are better discovered than planned.
Simon
Great stuff as usual!
I’d like to raise one point though.
Would the noteViewer and infoViewer objects not be great candiates for a moo Class?
They seem identical, so by generalising their method names (requestData, displayData) you could create one class and create two instances that allow a few options.
Right now it seems as though you’re duplicating two objects just to avoid using new Class()
Be sure to point out any obvious things I’m missing, I’ve only been using MooTools for a month or two.
Mark Obcena
Simon: You’re correct—right now they seems like identical objects, and it’s very tempting to create a new class and just make them instances of the class.
However, consider the MooTools
Elementtype and any MooTools class that implementsEvents. Elements and classes that use theEventsmixin are both event-producers. Would it be appropriate to implement anEventProducerclass, and simply subclass it for any and all classes or types that might produce events? I’m sure we’ll both shake our heads at the idea. :)One of the harder things to do in typography is kerning, which is essentially adjusting the spacing between characters. It’s a very visual operation, and can be tricky most of the time.
A great technique that’s useful in kerning is to flip the text you’re kerning around, so that you don’t recognize the letters. This way, you’ll lose any preconceived notions regarding the spacing of the letters because you won’t recognize them—giving you a chance to properly space them according to their shape.
When we think about reusability, one of the first things that come to our mind is similarities. “Which parts of this are similar to others? How can we reuse those parts?” But we have to flip the picture too: “What parts of these will be different? What can’t be reused?”
The differences are just as important as the similarities. Similarities are easy—we can use classes or mixins to implement them. But differences are harder to process, because they require differentiation: you’ll have to subclass what’s similar to fit the different. Unless there’s a huge number of items that are going to be similar, having to go through the process of unifying then differentiating them won’t be worth it.
In our case, the
infoViewerandnoteViewermodules seem really similar now, but that’s a product of the simplicity of the examples. But how about the differences that will arise from the future? OurinfoViewerwill obviously have to implement some kind of contact data management, which will involve adding fields, and editing existing ones. OurnoteViewerwill have the option to edit the note using only a single field.As new features are added, the more differentiation occurs, and the initial benefit of having them both be instances of the same class is no longer as great as it seem.
The solution therefore, would not to make them both classes, but to implement some sort of mixin functionality that will enable us to share code between modules without having to go through the painful differentiation process. This will be discussed in a future post. :)
Anton Suprun
There is another way. Instead of using Observer, I pass the object to be called by another class to the constructor of the class. If I ever want to change the object, all I need is another object with the same interface. This removes the necessity of observer, reducing total class count.
Mark Obcena
Anton: Dependency Injection is something that I’m personally mixed about.
Dependency Injection does indeed solve the direct dependency problem by allowing us to assign which object to use. By using Dependency Injection, we’ll be able to easily switch implementations by explicitly passing the particular object we’d like to see used in an operation.
However, remember that coupling isn’t just about object references: it’s really about how much an object knows about another object. Sure, we no longer have explicit references to particular objects, but the fact that we’re still calling the methods of the injected object directly inside our classes doesn’t really solve the problem of coupling (because that’s exactly what coupling is).
The use of Dependency Injection as the mechanism behind a module system will be ideal if and only if all the modules share the exact same API. If all modules are exactly the same in their interface implementation, then the coupling isn’t as bad because modules no longer need to know about the API of other modules since all of them are exactly the same. In other words, because it knows about itself, it automatically knows about all others.
But can we honestly say that this is the situation for most applications? Maybe—if we were building everything from scratch. Unfortunately, that’s not the case, which makes Dependency Injection not that ideal for general use in a module system.
Arieh
Mark: about your response to Simon - I would say that one of the main benefits of Class is the ability to extend it. When I plan my Classes, I sometimes start with an empty API (much like the way that Fx is implemented), and create extensions of that Class as a way to provide the differentiations.
For the above example, I would create an object that provides an initializer (aka Class). It may know how to receive a literal object as a set of optional parameters (aka Options). That initializer might call an attach method (or maybe an execute method). It might receive an Observer (which WILL be an extension of a known API, which you COULD validate - Interfaces are a key requirement for DI).
As for me - once I require an initializer, Evens and Options, that by itself is a reason to use Class.
John Henderson
Great article Keeto and perfect timing as I was wondering about how best to decouple objects. Keep up the good work.
shanebo
Keeto, yeah I like the connect() idea but am fuzzy on implementation. Does connect dynamically create an overlap object for whatever methods it’s told to fire, checking first to see if an object already exists for them? And where would the connect method be called from? It seems it would still be called from the item click event which puts us back into a coupling problem right?
Honestly non of the options are thrilling. There’s got to be a cleaner, more readable, simpler and more scalable way. I wonder if we’re over thinking it? A common developer problem :)
Mark Obcena
Arieh: Okay, the class discussion is getting a bit too complicated. Let me repeat: I am not against using classes, only misusing them.
In Simon’s question, he was asking whether the two modules should be better implemented as classes rather than object literals. My answer was about differentiation: the similarities between the two objects don’t outweigh their differences enough to warrant making them classes.
These differences aren’t about simple method overriding, like
Fx. Look at how subclasses ofFxdoes it: they override the original generic methods ofFxwith their own implementation. Subclassing here works, because the similarities (the API itself) are bigger than the differences (the particular implementation of the API methods).We can’t say the same for my two examples. Sure, they share similar methods right now, but can we implement a class that’s generic enough to simply warrant method overriding for their future editing and management APIs? I don’t think so—which is why I suggested that the similarities be made into mixins rather than superclasses.
As for DI, I’m still mixed about it overall. Maybe I’ll do some reading before I formulate my verdict.
John: Thanks. :)
Shane: Yes,
connectwould implement the observers and such, but theconnectfunction should be called outside the modules—that’s how you get the decoupling. It removes the connection code from outside the modules itself and into your application prelude code.Stick around until about Tuesday.. Maybe you’ll like what you’ll see. :)
shanebo
Tuesday is too far away keeto :) Sooner please. You’ve got me excited to solve this problem in my code bases.
Jostein Huglen
This is one of the best articles (and comments) I’ve seen about JavaScript code management in a long long time. Wish I had read this many years ago. I pretty much “invented” my own patterns, which I always suspected were nothing new at all, which this article shows. So nice to see a discussion about the pro’s and con’s of different patterns as well. Can’t wait to see more articles from you going even further.
Jostein Huglen
By the way… I picked the document object instead of the window object to be the mediator. Can’t remember why, right now. Any thoughts on that?
Ryan Florence
I kept reading thinking “Mark, just use the window … unless you’ve got some amazing trick up your sleeve.” I’ve been thinking a lot about the pub/sub pattern lately and how to get it in MooTools. Then I came to the same conclusion this great article did.
jeremy peck
Great article. I’ve struggled with object interactions in larger javascript applications before, and often times I wind up creating some generic “manager” class that gets passed into other dependent classes which helps to spread functionality around in a more meaningful manner.
I suppose this is most similar to the observer approach, though the abstraction of your example seems much cleaner.
I never though of using the window object as a mediator, this seems like a much better approach so long as one is able to come up with coherent naming conventions for the events that it fires.
I like the topic brought up by Barryvan and look forward to seeing some approaches for handling interactions in asynchronous applications.
Rolf-nl
Great article, very informative even though I was already doing stuff like this, but without really knowing what the ‘official’ terminology is (coming from an art school/graphic design background and coding is just a self taught thing because I liked it).
However, in your code you still have list-depending stuff in the modules like: window.addEvent(‘list.click’, this.requestContact.bind(this));
If there would be a thumbnail faces block thing as well as the list you would have to put in a faces.click event listener in both modules. Would that result in clean/independent code where more stuff is added?
Because I didn’t really know how to tackle this I’ve started using a sort of manager that glues the whole thing together. Like: var myList = new list({ container: $(‘foo’), list.clicked: function(id){ myApp.fireEvent(‘list.click’, id); } }); var myApp = new Events(); myApp.addEvent(‘list.click’, fn(id){ infoview.reqContact(id); });
Where myList fires the clicks that are send to myApp which in turn makes sure infoview does it’s job.
It kinda works but feels funny and wrong, even though there is no reference to “list” in the other modules…. i dunno.. any comments?
Luis Merino
Great article Keeto. I smiled while reading because I’ve used decoupling in so many ways in the past, I was enjoying the parity :) . Even with Decorator patterns and such, I have to say going so far off it’s sometimes not really necessary or advisable for that matter, given the nature of eased scope and prototypal language of Javascript :)
But in any case, here’s another more common example of observer pattern I used in the past:
This one has to be implemented in classes, but it has two advanges though over your example:
a) It allows decoupling as well, yet to use attach() to store events, the belonging Object has to be able to access a reference of the other Object (although that’d be better IMO than relying in a static variable like yours “var observer” in the example), since you know exactly what you will be dealing with, even if you are not fan of passing instances in the constructor. I say this because a simple setDelegationObject() method could help with decoupling for the case as well, and it’s nice.
b) Again, comparing against your example implementation, this one reduces the amount of “event” layers into 1 single level. Adding events to the object AND adding events to the observer is a little overdue; of course you are leveling it with the ability of using MT Events in its full potential – but it depends really whether you really need to remove events 1 by 1 for example – I have found this to be a rare case. It’s also about likes anyhow :)
Just wanted to give another perspective on the pattern, and congratulate you for the big effort.
Cheers!
Yaroslaff Fedin
Over-engineering at its finest :)
Multiple inheritance could save your soul from committing a crime against oneself. It’s not too late to drop your leaking abstractions, return to the basics, to things that are already there (Class inheritance & Events).
Seriously, come to think of it: Who is going to learn, understand and maintain one more of doing all the same things? If you think you need mixins and modules, why isn’t implementation based on mootools basics an option?
There still is a lesson to learn: Best code is the code that you dont need to write. Throwing more code and abstractions on a problem may not solve it, but hide it. There’re better ways to split your code to semi-independent pieces that are easily testable and don’t break mootools conventions. Adding one more class system is a low hanging fruit.
Mark Obcena
@Yaroslaff: It’s obvious from your response that you didn’t read the blog post.
I do know that your comment was directed at another topic, but in the light of this article (and even in that of that other topic) it seems unwittingly misguided and uninformed. I won’t delete it for the sake of posterity, but in the future please do read before you comment.
Mickael
Definitely the best Mootools blog I’ve seen.
Great stuff, well explained.
Jacob
Maybe I misunderstood, but in all of the examples with the observer/mediator mentioned, hasn’t name-spacing your events become a critical part?
If so, does this mean the brittleness has just been moved from direct object references to event names?
For example, if I have 10 different modules all with their own “onComplete” event, and some modules interested in module A’s onComplete and some interested in module B’s onComplete, when the window fires “onComplete” how do the wires not get crossed?
I love all of your articles, BTW. They’re exactly what I need as a JS developer.
Thanks Jacob
Mark Obcena
Jacob: Well, not exactly.
The brittleness of a directly referential style comes from the fact that one component knows too much about other components in the system. An object A that calls method X of object B directly, for example, has to know that that object B has method X.
Object A, then, assumes that B has method X. If A’s assumption is wrong (e.g. if B has no such method, if B changed the name of method X, if B was replaced with another object C, etc), then the whole thing goes awry. Therefore Object A depends on its knowledge of Object B in order for it to work properly.
On the other hand, the event-based component system doesn’t have the same drawback because A doesn’t need to know of B’s existence. It no longer assumes anything about any other component—it only expects that something will fire an event to which it listens.
So, if A is listening to event ‘x’ which is fired by B, A doesn’t need to know anything about B’s structure to perform its job. If we decide to replace B with C which also fires ‘x’, then there won’t need to be any changes in A.
The approaches are inherently different because the control is reversed. Instead of A actively calling stuff from B, it just waits passively until it’s signaled by B (or any other object) via an event. The brittleness then no longer lies upon the code, but on a higher-level contract regarding events—which removes actual relationships between the components.
It does seem that by prefixing modules, you just convert the dependency from a direct method call to a specific event name, but that’s only because of the style.
Namespacing is used in the examples to make sure that event names are properly distributed—but it’s not necessary as long as you properly differentiate the events. For example, isn’t very descriptive for modules to fire the same event named ‘onComplete’ via a mediator without any naming differentiation, because it doesn’t describe anything as it is. What was completed? What is this event for? There’s no indication.
On the other hand, if we use descriptive names like “onAnimationComplete” and “onUserCreationComplete,” we get the same benefits of namespacing without actually using namespaces. They’re generic enough to be used by any object and yet specific enough to differentiate an event type.
Jacob
Thank you for informative reply! I would like to elaborate on my situtation, and maybe see if this pattern will work (since it sounds like something I should be implementing)
We have a fairly large web front end for our workforce management system. In the app, I built a widget for working with a list of data (selecting, sorting, searching, etc) If I have, say, 4 of these on a given page, would I be able to use this pattern to have them communicate with the rest of the page since they’re 4 instances of the same class with the same event names? Or is that not the right situation to use the Observer or Mediator pattern?
Thanks again for the great blog and book, Jacob
Post a Reaction