design patterns for
Large-scale JAVASCRIPT

by Tiago Garcia @ Avenue Code
tgarcia@avenuecode.com

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)';
    }
  };
})();

See this live on Plunker!

_____________________________________________
 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


    _____________________________________________
     Design Patterns for Large-Scale Javascript

    CHALLENGE

    Write a large-scale client-side app using the patterns just introduced.

    1. Use your quiz app or fork mine from here:  https://github.com/tiagorg/quiz-app
    2. Read the instructions on https://github.com/tiagorg/quiz-app#refactor-to-design-patterns-for-large-scale-js
    3. 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,313