design patterns for
Large-scale JAVASCRIPT
Jan 27th, 2014
AGENDA
-
Design Patterns
- Javascript Design Patterns
- Constructor
- Module
- Façade
- Observer (custom event)
- Mediator (pub/sub)
_____________________________________________
Design Patterns for Large-Scale Javascript
AGENDA
- Large-scale Javascript
- Proposed architecture
- Applying patterns
_____________________________________________
Design Patterns for Large-Scale Javascript
Prerequisites
- Intermediate Javascript
- Advanced OOP
- Familiarity with Design Patterns
_____________________________________________
Design Patterns for Large-Scale Javascript
patterns
- Pattern:
- Proven solution to a certain class of problems in a specific context.
- Pre-conditions (requirements).
- Post-conditions (consequences).
- Pattern language: a common vocabulary.
- Network of connected patterns -> reuse.
_____________________________________________
Design Patterns for Large-Scale Javascript
design patterns
- Design Patterns target OO problems regarding objects creation, structure and behavior.
- Programming languages support them differently.
"With great power comes great responsibility"
Uncle Ben
_____________________________________________
Design Patterns for Large-Scale Javascript
javascript design patterns
- JS code is more complex today than ever before, as we have AJAX, SPA, MVC, unit testing, etc.
- Design patterns in JS:
- Avoid "spaghetti code" (hard to read and maintain for the lack of structure).
- Improve overall maintainability. It makes it clear to identify where to change the code.
- Enable more objective unit tests.
- Today we are covering some Design Patterns about Large-scale JS applications.
_____________________________________________
Design Patterns for Large-Scale Javascript
constructor
- A special method used to initialize a newly created object once memory has been allocated.
- JS is indeed OO but it differs from classic OO, and there are different ways to construct an object:
// object literal a.k.a. Singleton var myDog = {}; // ES5 Object.create method from prototype var myDog = Object.create(Dog.prototype); // constructor call (uses this to set properties) var myDog = new Dog();
_____________________________________________
Design Patterns for Large-Scale Javascript
basic constructor
- new must be used before invoking.
- this references the new object being created.
- Doesn't return anything.
function Dog(name, breed) { this.name = name; this.breed = breed; this.bark = function() { return this.name + ': woof, woof, woof!'; }; } var myDog = new Dog('Sherlock', 'beagle'); console.log(myDog.bark());
_____________________________________________
Design Patterns for Large-Scale Javascript
constructor & prototype
- Defining functions in constructor is not ideal, as the function will be redefined for each new instance.
- Using prototype, all instances can share functions.
function Dog(name, breed) { this.name = name; this.breed = breed; } Dog.prototype.bark = function() { return this.name + ': woof, woof!'; }; } var myDog = new Dog('Sherlock', 'beagle'); console.log(myDog.bark());
_____________________________________________
Design Patterns for Large-Scale Javascript
module
- Emulates classic OO classes to support public/private methods and variables inside a single object.
- Encapsulation achieved through closures.
var myDog = (function(name, breed) { var getBarkStyle = function() { return (breed === 'husky')? 'woooooow!': 'woof, woof!'; }; return { bark: function() { return name + ': ' + getBarkStyle(); } }; })('Sherlock', 'beagle'); console.log(myDog.bark());
_____________________________________________
Design Patterns for Large-Scale Javascript
Revealing module
-
Adds flexibility to switch methods and variables from public to private scope and vice-versa.
var myDog = (function(name, breed) { function getBarkStyle() { return (breed === 'husky')? 'woooooow!': 'woof, woof!'; }; function bark() { return name + ': ' + getBarkStyle(); }; return { name: name, bark: bark }; })('Sherlock', 'beagle'); console.log(myDog.bark());
_____________________________________________
Design Patterns for Large-Scale Javascript
module
- Highly enforced by JS community.
- First-class citizen in CommonJS and AMD.
define([], function() { return { sum: function(numberA, numberB) { return numberA + numberB; } }; });
- Will be part of EcmaScript 6 (Harmony) - the new version of JS.
_____________________________________________
Design Patterns for Large-Scale Javascript
Façade
-
Provides a convenient higher-level interface to a component, hiding its complexity and simplifying the API.
// Pure JS var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://code.jquery.com/jquery-latest.js', true); xhr.onreadystatechange = function() { if (this.readyState == 4 && this.status === 200) { console.log('success'); } }; // jQuery $.get('http://code.jquery.com/jquery-latest.js', function(data) { console.log('success'); });
_____________________________________________
Design Patterns for Large-Scale Javascript
Façade
var factorialFacade = (function() {
var intermediateResults = [0, 1];
function calculate(n, stats) {
if (intermediateResults[n] === undefined) {
stats.ops++;
intermediateResults[n] = n * calculate(n - 1, stats);
}
return intermediateResults[n];
}
return {
factorial: function(n) {
var stats = { ops: 0 },
result = (n > 0 && n % 1 === 0)? calculate(n, stats): 0;
return n + '! = ' + result + ' (' + stats.ops + ' operations)';
}
};
})();
_____________________________________________
Design Patterns for Large-Scale Javascript
Observer (custom event)
- A Subject object maintains a list of interested Observer objects, automatically notifying them of its changes.
var consoleObserver = function(event, msg) {
if (event === 'goal') {
console.log('Team ' + msg.team + ' just scored! ' + msg.score);
}
}
var htmlObserver = function(event, msg) {
if (event === 'goal') {
$('h3').text('Team ' + msg.team + ' just scored! ' + msg.score);
}
}
_____________________________________________
Design Patterns for Large-Scale Javascript
Observer (custom event)
var soccerMatchSubject = (function(teamA, teamB) {
var scoreTable = {};
scoreTable[teamA] = scoreTable[teamB] = 0;
var observers = [];
function notify() {
var args = arguments;
$.each(observers, function(index, observer) {
observer.callback.apply(observer.context, args);
});
}
return {
addObserver: function(fn) {
observers.push({ context: this, callback: fn });
},
//... continues on slide below
_____________________________________________
Design Patterns for Large-Scale Javascript
Observer (custom event)
//... from slide above
goal: function(team) {
scoreTable[team]++;
score = teamA + ' ' + scoreTable[teamA] + ' x ' +
scoreTable[teamB] + ' ' + teamB;
notify('goal', { team: team, score: score });
}
};
})('Brazil', 'Spain');
_____________________________________________
Design Patterns for Large-Scale Javascript
Observer (custom event)
- Now we just need to add the observers to listen.
soccerMatchSubject.addObserver(consoleObserver); soccerMatchSubject.addObserver(htmlObserver);
- When the subject method is called, it notifies all its listeners.
$('button').click(function(e) { e.preventDefault(); soccerMatchSubject.goal(this.innerHTML); });
- See this live on Plunker!
_____________________________________________
Design Patterns for Large-Scale Javascript
Mediator (pub/sub)
- Exposes an unified interface through which the different parts of a system may communicate.
- Notice how the Subject has references to the Observers -> coupling.
- Potential Garbage Collection issues: you must remove the Observer references from all the Subjects.
- A Mediator can decouple Subject and Observers by introducing a middle layer between them.
_____________________________________________
Design Patterns for Large-Scale Javascript
Mediator (pub/sub)
var mediator = { channels: {}, subscribe: function(channel, fn) { if (!mediator.channels[channel]) mediator.channels[channel] = []; mediator.channels[channel].push({ context: this, callback: fn }); }, publish: function(channel){ if (!mediator.channels[channel]) return; var args = Array.prototype.slice.call(arguments, 1); $.each(mediator.channels[channel], function(index, subscriber) { subscriber.callback.apply(subscriber.context, args); }); }, installTo: function(publisher){ publisher.subscribe = mediator.subscribe; publisher.publish = mediator.publish; }
};
_____________________________________________
Design Patterns for Large-Scale Javascript
Mediator (pub/sub)
var soccerMatchSubject = (function(teamA, teamB) {
var scoreTable = {};
scoreTable[teamA] = scoreTable[teamB] = 0;
return {
goal: function(team) {
scoreTable[team]++;
score = teamA + ' ' + scoreTable[teamA] + ' x ' +
scoreTable[teamB] + ' ' + teamB;
this.publish('loggers', 'goal', { team: team, score: score });
}
};
})('Brazil', 'Spain');
mediator.installTo(soccerMatchSubject);
_____________________________________________
Design Patterns for Large-Scale Javascript
Mediator (pub/sub)
- Again, we just need to add the observers to listen.
soccerMatchSubject.subscribe('loggers', consoleObserver); soccerMatchSubject.subscribe('loggers', htmlObserver);
- The observers are the same, the event triggering is also the same.
- Notice how the Mediator is on top of control. It is serving as a pub/sub infrastructure for components.
- See this live on Plunker!
_____________________________________________
Design Patterns for Large-Scale Javascript
large-scale javascript
- Large-scale is not about LOC or app size.
- Large-scale is about maintainability and architectural support for expanding keeping the same complexity.
- To make this possible we need:
- cohesive modules
- low coupling between modules
- module independency
- module replaceability
- module security
- some infrastructure to control the modules
_____________________________________________
Design Patterns for Large-Scale Javascript
proposed architecture
- Employing patterns Module, Façade and Mediator:
- Module to encapsulate components / features.
- Façade to create a secure sandbox around the Module.
- Mediator to orchestrate the Modules in terms of:
- publish and subscribe
- lifecycle (add, remove, start, stop)
- delivering messages
- A Module can be both publisher and subscriber.
_____________________________________________
Design Patterns for Large-Scale Javascript
applying patterns
var NewEmailModule = function(from) {
return {
newEmail: function(email) {
email.from = from;
this.publish('email', { email : email });
}
};
};
var myNewEmail = NewEmailModule('tgarcia@avenuecode.com');
mediator.installTo(myNewEmail);
_____________________________________________
Design Patterns for Large-Scale Javascript
applying patterns
var MailboxModule = function(owner, channel) {
var emails = [];
return {
receiveEmail: function(message) {
if (message.email.to === owner) {
emails.push(message.email);
this.publish(channel, { owner: owner, emails: emails });
}
}
};
};
var myMailbox = MailboxModule('tgarcia@avenuecode.com', 'my-mailbox');
mediator.installTo(myMailbox);
mediator.subscribe('email', myMailbox.receiveEmail);
_____________________________________________
Design Patterns for Large-Scale Javascript
applying patterns
var RenderMailboxModule = function(selector) { var el = $(selector); return { render: function(message) { var frag = $(document.createDocumentFragment()); $.each(message.emails, function(index, email) { frag.append('<h3>[' + index + '] ' + email.subject + '</h3>') .append('<h4>From: ' + email.from + '</h4>') .append('<p>' + email.content + '</p>'); }); el.html(frag); } }; };
var myRenderMailbox = RenderMailboxModule('#my-mailbox'); mediator.subscribe('my-mailbox', myRenderMailbox.render);
_____________________________________________
Design Patterns for Large-Scale Javascript
applying patterns
- This is the event triggering:
$('button').click(function(e) { e.preventDefault(); var email = { to: $('input[name=to]').val(), subject: $('input[name=subject]').val(), content: $('textarea[name=content]').val() }; myNewEmail.newEmail(email); }); });
- Notice how a module doesn't know about others.
-
See this live on Plunker!
_____________________________________________
Design Patterns for Large-Scale Javascript
Conclusion
- Design Patterns provide recommended solutions for common problems in OO.
- Javascript badly needs that to avoid "spaghetti code".
- Patterns as Module, Façade and Mediator can be combined in a solid solution for Large-Scale JS.
- It is very important to avoid coupling at all costs in order to leverage scalability and maintainability.
_____________________________________________
Design Patterns for Large-Scale Javascript
Learn more
- Learning JavaScript Design Patterns - Addy Osmani - addyosmani.com/resources/essentialjsdesignpatterns
- Patterns for Large-Scale JavaScript Application Architecture - addyosmani.com/largescalejavascript
- Javascript Patterns - Stoyan Stefanov - shop.oreilly.com/product/9780596806767.do
- AuraJS - aurajs.com
_____________________________________________
Design Patterns for Large-Scale Javascript
CHALLENGE
Write a large-scale client-side app using the patterns just introduced.
- Use your quiz app or fork mine from here: https://github.com/tiagorg/quiz-app
- Read the instructions on https://github.com/tiagorg/quiz-app#refactor-to-design-patterns-for-large-scale-js
- Send me the solution in a GitHub repo.
_____________________________________________
Design Patterns for Large-Scale Javascript
Design Patterns for Large-Scale Javascript
By Avenue Code
Design Patterns for Large-Scale Javascript
Learn the essential design patterns for large-scale Javascript applications. By Tiago Garcia
- 7,335