Javascript Most Common Patterns

April 5, 2016

  • Introduction
  • Objects in Javascript
  • Creational Design Patterns
  • Structural Design Patterns
  • Behavioral Design Patterns

Introduction

“… each pattern represents our current best guess as to what arrangement of the physical environment will work to sovle the problem represented.” Cristopher Alexander

A Pattern Language: Towns, Buildings, Construction (1977) described a practical architectural system in a form that a theoretical mathematician or computer scientist might call a generative grammar.

“The empirical questions center on the problem

  • does it occur and is it felt in the way we have described it?”

“and the solution -does the arrangement we propose resolve the problem?”

The Gang of Four

Problems Soluctions
Designing Service Layers Module Pattern
Overly Complicated Object Interfaces Façade Pattern
Visibility Into State Changes Observer Pattern

What constitutes a pattern

  • It solves a problem
  • It is a proven concept
  • The solution is not obvious
  • It described a relationship
  • It has a significant human component

Patterns also give us a common vocabulary

Types of patterns

  • Creational (new instances of an object)
    • Constructor
    • Module
    • Factory
    • Singleton
  • Structural (makeup of the objects itselves)
    • Decorator
    • Façade
    • Flyweight
  • Behavioral (how objects relate to each other)
    • Command
    • Mediator
    • Observer

Objects in Javascript

Creating Objects

var obj = {};
var nextObj = Object.create(Object.prototype); // we usually use it for inheritance
var lastObj = new Object(); // not very used because in ES6 we have 'classes' now

Reading and Writing Attributes

var obj = {};
obj.param = 'new value';
console.log(obj.param);
var obj = {};
obj['param'] = 'new value';
console.log(obj['param']);
var obj = {};
var val = 'value';
obj[val] = 'new value';
console.log(obj[val]); // new value

Define Property

Object.defineProperty(obj, 'name', {
  value: 'my name',
  writable: true,
  enumerable: true,
  configurable: true
});
var task = {
  title: 'My Title',
  description: 'My Description'
};

Object.defineProperty(task, 'toString', {
  value: function() {
    return this.title + ' ' + this.description;
  },
  writable: false,
  enumerable: false,
  configurable: false
});

// the following won't work because we already set the property to false
Object.defineProperty(task, 'toString', {
  enumerable: true
});

task.toString = 'hi'; // it won't change because we defined the property as not writable

console.log(Object.keys(task)); // it won't show toString because we've defined as not enumerable

Inheritance

var task = {
  title: 'My Title',
  description: 'My Description'
};

Object.defineProperty(task, 'toString', {
  value: function() {
    return this.title + ' ' + this.description;
  },
  writable: false,
  enumerable: false,
  configurable: false
});
var urgentTask = Object.create(task);

Object.defineProperty(urgentTask, 'toString', {
  value: function() {
    return this.title + ' ' + is urgent;
  },
  writable: false,
  enumerable: false,
  configurable: false
});

console.log(urgentTask.toString()); // My Title is urgent

Creational Design Patterns

Constructor Pattern

Create objects from functions. The name of our function will be the name of our objects.

When using the new keyword

  • Creates a new object
  • Links to an object prototype
  • Bind ‘this’ to the new object scope
  • Implicitly returns ‘this’
function MyObject(param1, param2) {
  this.param1 = param1;
  this.param2 = param2;
  this.toString = function () { // <-- check Prototypes section
    return this.param1 + ' ' + this.param2;
  }
}

Example

var Task = function (name) {
  this.name = name;
  this.completed = false;

  this.complete = function () {
    console.log('completing task: '+this.name);
    this.completed = true;
  };

  this.save = function () {
    console.log('saving Task: ' + this.name);
  };
};

var task1 = new Task('Go to the Mall');
var task2 = new Task('Buy food');
var task3 = new Task('Do some exercise');
var task4 = new Task('Meet Jane');

task1.complete();
task1.save();
task2.save();
task3.save();
task4.save();

Prototypes

An encapsulation of properties that an object links to.

Signature

ClassName.prototype.methodName = function (arguments) {};
var Task = function (name) {
  this.name = name;
  this.completed = false;
};

Task.prototype.complete = function () {
  console.log('completing task: '+this.name);
  this.completed = true;
};

Task.prototype.save = function () {
  console.log('saving Task: ' + this.name);
};

Classes

If we are not using node we may need use a Transpiler like Babel.

'use strict'

class Task {
  constructor(name) {
    this.name = name;
    this.completed = false;
  };

  complete() {
    console.log('completing task: '+this.name);
    this.completed = true;
  };

  save() {
    console.log('saving Task: ' + this.name);
  };
}

module.exports = Task;

Constructor in Angular

There are several ways of doing this. We could also use value for example.

(function() {
  'use strict';

  angular.module("myApp")
  .factory('Task',function () {
    var Task = function (name) {
      this.name = name;
      this.completed = false;
    };

    Task.prototype.complete = function () {
      console.log('completing task: '+this.name);
      this.completed = true;
    };

    Task.prototype.save = function () {
      console.log('saving Task: ' + this.name);
    };

    return Task;
  });

})();

Module Pattern

In it’s core is just an object literal.

We have one instance of it. Generally we use if tor services.

var Module = function () {
  var privateVar = 'I am private ...';
  return {
    method1: function () {},
    method2: function () {}
  }
}

Revealing Module Pattern

var repo = function () {

  var tasks = {};

  return {
    get: get,
    save: save
  };

  function get(id) {
    console.log('Getting task '+ id);
    return tasks[id];
  }

  function save(task) {
    console.log('Saving ' + task.name + ' to the db');
    tasks[task.id] = task;
  }
}

module.exports = repo();

Module Pattern in Angular

(function() {
  'use strict';

  var app = angular.module("myApp");

  var taskRepo = function () {
    var tasks = {};

    return {
      get: get,
      save: save
    };

    function get(id) {
      console.log('Getting task '+ id);
      return tasks[id];
    }

    function save(task) {
      console.log('Saving ' + task.name + ' to the db');
      tasks[task.id] = task;
    }
  }

  app.service('taskRepo',taskRepo);

  
})();

Factory Pattern

  • Simplifies object creation
  • Creating different objects based on need
  • Repository creation
var repoFactory = function () {
  var repos = this; // used as cache
  // repoList could come from anywhere
  var repoList = [
    {name: 'task', source: './taskRepository'},
    {name: 'user', source: './userRepository'},
    {name: 'project', source: './projectRepository'}
  ];

  repoList.forEach(function(repo) {
    repos[repo.name] = require(repo.source)();
  });
};

module.exports = new repoFactory;

Singleton Pattern

Used to restrict an object to one instance of that object across the application.

'use strict';

var TaskRepo = (function () {
  var taskRepo;
  function createRepo() {
    var taskRepo = new Object('Task');
    return taskRepo;
  }
  return {
    getInstance: function () {
      if (!taskRepo) {
        taskRepo = createRepo();
      }
      return taskRepo;
    }
  }
})();

Singletons in Node

Node.js that uses CommonJS, caches everything that is used in a require.

Modules are cached after the first time they are loaded.

If you want to have a module execute code multiple times, then export a function, an call that function.

So if we want to have a Singleton we have to do the opposite: call the function.

// repo.js
'use strict';

var repo = function () {

  var called = 0;

  var save = function (task) {
    called++;
    console.log('Saving ' + task + ' Called ' + called + ' times');
  }
  console.log('newing up task repo');
  return {
    save: save
  }
}
module.exports = new repo; // we make it a Singleton

Now everytime we require repo.js will get the same instance.

Singletons in Angular

All Services are singletons; they get instantiated once per app. They can be of any type, whether it be a primitive, object literal, function, or even an instance of a custom type.

The value, factory, service, constant, and provider methods are all providers. They teach the Injector how to instantiate the Services.

Stackoverflow service vs provider vs factory

Structural Design Patterns

Concerned with how objects are made up and simplify relationships between objects.

  • Deal with relationship of objects
    • Extend functionality
    • Simplify functionality

Decorator

Used to add new functionality to an existing object, without being obtrusive.

  • More complete inheritance
  • Wraps an object
  • Protects existing objects
  • Allows extended functionality

Decorating a single Object

var Task = function (name) {
  // ...
}

Task.prototype.complete = function () {
  // ...
}

Task.prototype.save = function () {
  // ...
}

Now we decorate this object to create a new one

var myTask = new Task('Urgent Task');

urgentTask.priority = 2; // new attribute

// new method notify
urgentTask.notify = function() {
  console.log('notifying...');
};

// calling the original save
urgentTask.save = function () {
  this.notify();
  Task.prototype.save.call(this); // we call right here
};

Creating multiple decorated Objects (subtype)

var Task = require('./common/task');

var UrgentTask = function (name, priority) {
    // Calling the parent constructor
    Task.call(this, name);
    this.priority = priority;
};

// Overwritting the prototype so we don't 'mess' witht the parent
var _p = UrgentTask.prototype = Object.create(Task.prototype);

 _p.notify = function () {
    console.log('notifying...');
 };

_p.save = function() {
    this.notify();
    Task.prototype.save.call(this); // we call the original save here
};

module.exports = UrgentTask;

Decorating objects in Angular

function TaskController(Task,UrgentTask, TaskRepository) {
  var ctrl = this;
  ctrl.tasks = [];
  ctrl.taks.push(new Task(TaskRepositoryget(1)));
  ctrl.taks.push(new Task(TaskRepositoryget(2)));
  ctrl.taks.push(new UrgentTask(TaskRepositoryget(3)));
  ctrl.taks.push(new UrgentTask(TaskRepositoryget(4)));
}
(function() {
    'use strict';

    angular.module("myApp")
    // pay attention to the dependency
    .factory('UrgentTask',['Task',UrgentTask]);

    function UrgentTask(Task) {
        var UrgentTask = function (name, priority) {
            // calling the 'parent'
            Task.call(this,name);
            this.priority = priority;
        };

        // create a new independent prototype
        var _p = UrgentTask.prototype = Object.create(Task.prototype);

        // decorating
        _p.notify = function () {
            console.log('notifying important people');
        };

        _p.save = function () {
            this.notify();
            console.log('do special stuff before saving');
            Task.prototype.save.call(this);
        };

        return UrgentTask;
    }
})();

Decorating Angular Services

Mainly we decorate an Angular Service so we can do some configuration on it.

(function() {
  'use strict';

  var app = angular.module("myApp");
  
  // $delegate is the original service
  app.decorator('taskRepo',function ($delegate) {
    var oldSave = $delegate.save;
    $delegate.save = function (task) {
      console.log('Special loggin for task ' + task.name);
      oldSave(task);
    };

    return $delegate;
  });
})();

Facade

Used to rpovied a simplified interface to a complicated system.

  • Facade hides the complexity from us
  • Simplifies the interface
  • jQuery is a good example (think of the DOM)

The main difference between the decorator and the facade is that we are not adding funtionality, we are createing a new interface.

var TaskService = function () {
  // ...
};

var TakServiceWrapper = function() {
  // we are using the module revealing pattern here
  
  return {
    completeAndNotify: completeAndNotify
  };

  function completeAndNotify (myTask) {
    TaskService.complete(myTask);
    if (myTask.completed == true) {
      TaskService.setCompleteDate(myTask);
      TaskService.notifyCompletion(myTask, myTask.user);
      TaskService.save(myTask);
    }
  }
};

module.exports = TakServiceWrapper();

Facade in Angular

(function() {
  'use strict';

  angular.module("myApp")
  .service('taskServiceFacade',['taskService',taskServiceFacade]);

  function taskServiceFacade(taskService) {

    return {
      completeAndNotify: completeAndNotify
    };

    function completeAndNotify() {
      TaskService.complete(task);
      if (task.completed === true) {
        TaskService.setCompleteDate(task);
        TaskService.notifyCompletion(task,task.user);
        TaskService.save(task);
      }
    }
  }
})();

Flyweight

Conserves memory by sharing portions of an object between objects.

  • Flyweight shares data accross objects
  • Results in a smaller memory footprint
  • Bunt only if you have laaaaarge numbers of objects

Original object

'use strict';

var Task = function (data) {
  this.name = data.name;
  this.priority = data.priority;
  this.project = data.project;
  this.user = data.user;
  this.completed = data.completed;
};

module.exports = Task;

New Object (without the common parts)

'use strict';

var factory = require('./flyweightFactory');

var Task = function (data) {
  this.flyweight = factory.get(data.project,data.priority,data.user,data.completed);
  this.name = data.name;
};

module.exports = Task;

Keeping the common parts for all the objects

'use strict';

var FlyWeight = require('./flyweight');

module.exports = function (){
  var flyweights = {};
  
  return {
    get: get,
    getCount: getCount
  };

  function getCount() {
    var count = 0;
    for (var f in flyweights) count++;
      return count;
  }

  function get(project, priority, user, completed) {
    var index = project+priority+user+completed;
    if(!flyweights[index]) {
      flyweights[index] = new FlyWeight(project,priority,user,completed);
    }
    return flyweights[index];
  }
}();

Behavioral Design Patterns

Concerned with the assignment of responsabilities between objects and how they communicate.

  • Deals with the responsibilities of objects
  • Help objects cooperate
  • Assigns clear hierarchy
  • Can encapsulate requests

Observer Pattern

Allows a collection of objects to watch an object and be notified of changes.

  • Allows for loosely coupled system
  • One object is the focal point
  • Group of objects watch for changes
'use strict';

var ObservableTask = require('./observableTask');
var Notification = require('./observers/notificationService');
var Logging = require('./observers/loggingService');
var Auditing = require('./observers/auditingService');

var task1 = new ObservableTask({
  name: 'whatever task',
  user: 'ppages'
});


var not = new Notification();
var ls = new Logging();
var audit = new Auditing();

task1.addObserver(not.update);
task1.addObserver(ls.update);
task1.addObserver(audit.update);

task1.save();

task1.removeObserver(ls.update);
task1.removeObserver(audit.update);

task1.save();

Observer example

module.exports = function () {
  var message = 'Auditing ';
  this.update = function (task) {
    console.log(message + task.user + ' for task ' + task.name);
  }
};

Subject

var Task = require('./task');
var ObserverList = require('./observerList');

var ObservableTask = function (data) {
  Task.call(this, data);
  this.observers = new ObserverList();
};

var _p = ObservableTask.prototype = Object.create(Task.prototype);

_p.addObserver = function (observer) {
  this.observers.add(observer);
};

_p.notify = function (context) {
  var observerCount  = this.observers.count();
  for (var i = observerCount - 1; i >= 0; i--) {
    this.observers.get(i)(context);
  }
};

_p.save = function () {
  this.notify(this);
  Task.prototype.save.call(this);
};

_p.removeObserver = function (observer) {
  this.observers.removeAt(this.observers.indexOf(observer,0));
};

module.exports = ObservableTask;

ObservableList

var ObserverList = function () {
  this.observerList = [];
};

var _p = ObserverList.prototype;

_p.add = function (observer) {
  return this.observerList.push(observer);
}

_p.get = function (index) {
  if (index > -1 && index < this.observerList.length ) {
    return this.observerList[index];
  }
}

_p.count = function () {
  return this.observerList.length;
};

_p.removeAt = function (index) {
  this.observerList.splice(index,1);
};

_p.indexOf = function (obj, startIndex) {
  var i = startIndex;

  while (i < this.observerList.length) {
    if (this.observerList[i] === obj) {
      return i;
    }
    i++;
  }

  return -1;
};

module.exports = ObserverList;

Mediator Pattern

Controls communication between objects so neither object has to be coupled to the others.

  • Allows for loosely coupled system
  • One object manages all communication
  • Many to many relationship
'use strict';

var Task = function(data) {
  this.name = data.name;
  this.priority = data.priority;
  this.project = data.project;
  this.user = data.user;
  this.completed = data.completed;
};

var _p = Task.prototype;

_p.complete = function () {
  console.log('completing task: ' + this.name);
  this.completed = true;
};

_p.save = function () {
  console.log('saving task: ' + this.name);
};

module.exports = Task;
var mediator = function () {
  var channels = {};

  return {
    channels: channels,
    subscribe: subscribe,
    publish: publish
  };

  function subscribe (channel, context, func) {
    if (!channels[channel]) {
      channels[channel] = [];
    }
    channels[channel].push({
      context: context,
      func: func
    });
  }

  function publish (channel) {
    if (!channels[channel]) {
      return false;
    }

    var args = Array.prototype.slice.call(arguments, 1); // getting rid of the first argument 'channel'

    for (var i = 0; i < channels[channel].length; i++) {
      var sub = channels[channel][i];
      sub.func.apply(sub.context, args);
    }
  }

};

module.exports = mediator();
var mediator = require('./mediator');

var Task = require('./task');

// overwritting original complete function
Task.prototype.complete = function (){
  console.log('Completed task '+this.name);
  mediator.publish('complete', this, 'hellooooooo');
};

var Not = new require('./observers/notificationService');
var Ls = new require('./observers/loggingService');
var Audit = new require('./observers/auditingService');

var task1 = new Task({
  name: 'demo task',
  user: 'John Doe'
});

var task2 = new Task({
  name: 'buy toilet papper',
  user: 'Pere Pages'
});

var not = new Not();
var ls = new Ls();
var audit = new Audit();

mediator.subscribe('complete', not, not.update); // channel, context, function
mediator.subscribe('complete', ls, ls.update); 
mediator.subscribe('complete', audit, audit.update); 

task1.complete();
task2.complete();

Command Pattern

Encapsulates the calling of a metohd as an object.

Fully decouples the execution from the implemenation.

  • Allows for less fragile implementations
  • Support undo operations
  • Suports auditing and logging of opperations
'use strict';

var repo = function () {

    var tasks = {};
    var commands = [];
    var me = {
        get: get,
        save: save
    };

    return {
        execute: execute,
        tasks: tasks,
        commands: commands,
        replay: replay
    };

    function get (id) {
        console.log('Getting task ' + id);
        return {
            name: 'new task from the db'
        };
    }

    function save (task) {
        tasks[task.id] = task;
        console.log('Saving "' + task.name + '" to the db');
    }

    function executeNoLog (name) {
        var args = Array.prototype.slice.call(arguments, 1);

        if (me[name]) {
            return me[name].apply(repo, args);
        }
        return false;
    }

    function execute (name) {
        var args = Array.prototype.slice.call(arguments, 1);

        commands.push({
            name: name,
            obj: args[0]
        });

        if (me[name]) {
            return me[name].apply(repo, args);
        }
        return false;
    }

    function replay () {
        for (var i = 0; i < commands.length; i++) {
            var command = commands[i];

            executeNoLog(command.name, command.obj);
        }
    }
}();

module.exports = repo;
var command = require('./repo');

command.execute('save', {
    id: 1,
    name: 'Task 1',
    completed: false
});
command.execute('save', {
    id: 2,
    name: 'Task 2',
    completed: false
});
command.execute('save', {
    id: 3,
    name: 'Task 3',
    completed: false
});
command.execute('save', {
    id: 4,
    name: 'Task 4',
    completed: false
});

console.log(command.tasks);
console.log(command.commands);
command.replay();