Design patterns in javascript
description
Transcript of Design patterns in javascript
Design patterns in Javascript
Miao Siyu
Why need design patterns
• Easy & nature: read, use/follow, extend/re-use, co-operator(expose api, split work…)…
• Efficiency & memory cost/gc…
books• 'Patterns Of Enterprise Application
Architecture' Martin Fowler• 'JavaScript Patterns' Stoyan Stefanov• 'Pro JavaScript Design Patterns' Ross Harmes
and Dustin Diaz• 'Learning JavaScript Design Patterns' Addy
Osmani• …
Categories Of Design Pattern
• Creational Design Patterns Constructor, Factory, Abstract, Prototype, Singleton and Builder
• Structural Design Patterns Decorator, Facade, Flyweight, Adapter and Proxy
• Behavioral Design Patterns Iterator, Mediator, Observer and Visitor
Create an object• var newObject = {};• var newObject = Object.create(…);• var newObject = new Class();
http://dmitrysoshnikov.com/ecmascript/javascript-the-core/
Define properties• newObject.someKey = …;• newObject[“someKey”] = …;• Object.defineProperty(newObject, “someKey”,{ // property attributes here: value, writable… })• function Car(model, color){ this.model = model; this.color = color } Car.prototype.run = function(){console.log(“run”)};
Prototype pattern
• var anotherCar = Object.create( someCar );• var beget = (function () { function F() {}; return function (proto) { F.prototype = proto; return new F(); }; })();
Clone using prototype: avoid the inherent cost
Singleton patternvar Singleton = (function () {
var instantiated;function init() {
return {publicMethod: function () {},publicProperty: 'test'
};}return {
getInstance: function () {if (!instantiated) {
instantiated = init();}return instantiated;
}};
})();Singleton.getInstance().publicMethod();
Anonymous Function
Module patternvar myNamespace = (function () {
var myPrivateVar = 0;var myPrivateMethod = function (someText) {
console.log(someText);};return {
myPublicVar: "foo",myPublicFunction: function (bar) {
myPrivateVar++;myPrivateMethod(bar);
}};
})();
Loose Augumentation:
var MODULE = (function (my) { // add capabilities...return my;
}(MODULE || {}));
http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth
Cloning & Inheritance
for (key in old) { if (old.hasOwnProperty(key)) { my[key] = old[key]; } }
Option 1: var myApplication = myApplication || {};Option 2 if(!MyApplication) MyApplication = {};Option 3: var myApplication = myApplication = myApplication || {}Option 4: myApplication || (myApplication = {});Option 5: var myApplication = myApplication === undefined ? {} : myApplication;
The Revealing Module Patternvar myRevealingModule = (function(){
var name = 'John Smith';function setPerson (personName) {
name = personName;}function getPerson () {
return name;}return {
// set: setPerson,get: getPerson
};}());
Advantage: same style
Disadvantage: when over-writting
Observer Pattern & Mediator Patternvar pubsub = {};(function(q) { var topics = {}, subUid = -1; q.publish = function( topic, args ) { if ( !topics[topic] ) { return false; } var subscribers = topics[topic], var tempArr; if(subscribers ) { tempArr = subscribers .slice(0); while (tempArr .length) { tempArr.pop().func(topic, args); } } return this; };
q.subscribe = function( topic, func ) { if (!topics[topic]) { topics[topic] = []; } var token = (++subUid).toString(); topics[topic].push({ token: token, func: func }); return token; };
q.unsubscribe = function( token ) { for ( var m in topics ) { if ( topics[m] ) { for (var i = 0, j = topics[m].length; i < j; i++) { if (topics[m][i].token === token) { topics[m].splice(i, 1); return token; } } } } return this; };}( pubsub ));
Mediator Pattern: Colleague – communicate by -> Mediator
Observer - listener to -> Mediator
Backbone.js: listen to changes in model
Mediator:Communication between Colleague
Façade: N-to-1, listener to a event hub
Command Pattern(function(){ var CarManager = { requestInfo: function( model, id ){}, buyVehicle: function( model, id ){}, arrangeViewing: function( model, id ){} };})();
CarManager.execute = function (name) { // we can log the commands here, also implements an undo function return CarManager[name] && CarManager[name].apply(CarManager, [].slice.call(arguments, 1));};
CarManager.execute("arrangeViewing", "Ferrari", "14523");CarManager.execute("requestInfo", "Ford Escort", "34232");CarManager.execute("buyVehicle", "Ford Escort", "34232");
We can keep track of the commands, also record a game reply
Façade Patternvar foo = document.getElementById('foo'); foo.style.color = 'red'; foo.style.width = '150px'; var bar = document.getElementById('bar'); bar.style.color = 'red'; bar.style.width = '150px'; var baz = document.getElementById('baz'); baz.style.color = 'red'; baz.style.width = '150px';
function setStyles(elements, styles) { for (var i=0, length = elements.length; i < length; i++) { var element = document.getElementById(elements[i]); for (var property in styles) { element.style[property] = styles[property]; } }} //Now you can just write this:setStyles(['foo', 'bar', 'baz'], { color: 'red', width: '150px'});
Very common in Jquery: .css(), .animate()…
Factory Patternvar AbstractVehicleFactory = (function () {
var types = {};return {
getVehicle: function (type, customizations) {// we can keep track of vehicle or get vehicle by options instead of type …var Vehicle = types[type];return (Vehicle) ? return new Vehicle(customizations) : null;
},registerVehicle: function (type, Vehicle) {
var proto = Vehicle.prototype;if (proto.drive && proto.breakDown) {
types[type] = Vehicle;}return AbstractVehicleFactory;
}};
})();
AbstractVehicleFactory.registerVehicle("car", Car);AbstractVehicleFactory.registerVehicle("truck", Truck);var car = AbstractVehicleFactory.getVehicle("car", { color: "yellow", turbo: true });var truck = AbstractVehicleFactory.getVehicle("truck", { monster: true, cylinders: 12 });
Give me a android phone with 4.1 inch screen around S$400
Mixin Patternfunction augment( receivingClass, givingClass ) { if ( arguments[2] ) { for (var i=2, len=arguments.length; i<len; i++) { receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]]; } } else { for ( var methodName in givingClass.prototype ) { if ( !receivingClass.prototype[methodName] ) { receivingClass.prototype[methodName] = givingClass.prototype[methodName]; } } }}
var Car = function( settings ){this.model = settings.model ;this.colour = settings.colour ;
};
var Mixin = function(){};Mixin.prototype = {
driveForward: function(){},driveBackward: function(){}
};
augment( Car, Mixin,'driveForward','driveBackward' );
var vehicle = new Car({model:'Ford', colour:'blue'});
vehicle.driveForward();vehicle.driveBackward();
We have update our factory to producetanks with new weapon
Class level
Decorator Pattern
• SubClassingvar Person = function( firstName , lastName ){
this.firstName = firstName;this.lastName = lastName;this.gender = 'male'
};
var Superhero = function( firstName, lastName , powers ){Person.call(this, firstName, lastName);this.powers = powers;
}SuperHero.prototype = Object.create(Person.prototype);
var superman = new Superhero( "Clark" ,"Kent" , ['flight','heat-vision'] );
Object level
function Vehicle( vehicleType ){ this.vehicleType = vehicleType || 'car', this.model = 'default', this.license = '00000-000'}function makeTruck(truck){ truck.setModel =function( modelName ){} truck.setColor = function( color ){} return truck;}var truck = makeTruck(new Vehicle());
function MacBook() { this.cost = function () { return 997; }; this.screenSize = function () { return 13.3; };}
function Memory( macbook ) { var v = macbook.cost(); macbook.cost = function() { return v + 75 ;}}function LargeScreen( macbook ){ var v = macbook.cost(); macbook.cost = function(){ return v + 100; }; macbook.screenSize=function(){ return 14.8; };}function Insurance( macbook ){var v = macbook.cost(); macbook.cost = function(){ return v + 250; };}
var mb = new MacBook();Memory(mb);LargeScreen(mb);Insurance(mb);
• Simple decorator • Mulit decorator
user can wear equips to change attrand gain new ablitities
Pseudo-classical decorators: interfacevar TodoList = new Interface('Composite', ['add', 'remove']);var TodoItem = new Interface('TodoItem', ['save']);
var myTodoList = function(id, method, action) { // implements TodoList, TodoItem};
function addTodo( todoInstance ) { Interface.ensureImplements(todoInstance, TodoList, TodoItem); // …}
https://gist.github.com/1057989var Interface = function(name, methods){…}Interface.ensureImplements = function(obj){…}
Make sure that only archers can enter certain stage
Flyweight patternvar Book = function(){ this.id = id; this.title = title; this.author = author; this.genre = genre; this.pageCount = pageCount; this.publisherID = publisherID; this.ISBN = ISBN; this.checkoutDate = checkoutDate; this.checkoutMember = checkoutMember; this.dueReturnDate = dueReturnDate; this.availability = availability;};
Book info:var Book = function () { this.title = title; this.author = author; this.genre = genre; this.pageCount = pageCount; this.publisherID = publisherID; this.ISBN = ISBN;};
Share date as much as possible
Checkout Info:
var BookFactory = (function () { var existingBooks = {}; return { createBook: function () { var existingBook = existingBooks[ISBN]; if (existingBook) { return existingBook; } else { var book = new Book(); existingBooks[ISBN] = book; return book; } } }});
var BookRecordManager = (function () { var bookRecordDatabase = {}; return {
addBookRecord: function ( var book = bookFactory.createBook(); bookRecordDatabase[id] = { checkoutMember: checkoutMember, checkoutDate: checkoutDate, dueReturnDate: dueReturnDate, availability: availability, book: book }; },
updateCheckoutStatus: function () {}, extendCheckoutPeriod: function () {}, isPastDue: function (bookID) {} };});
Object PoolPool = {arr:[]};
Pool.pop = function(){ if(Pool.arr.length > 0){ return Pool.arr.pop().wake(); }else{ return new Obj(); }}Pool.push= function(obj){ Pool.arr.push(obj); obj.sleep();}Object pools are used to avoid the instantiation cost of creating new objects by re-using existing ones. Avoid creating objects is gc-friendly.
May have hundreds of planes through the whole game, but not more than 20 at the same time.
Generator a plan: pop()Leave screen or destroyed: push()
Reuse resource when it has a high cost to make or release, eg. connection pool
MV* pattern• V: Templing
Handlebars, Underscore…• *: bind event, route, render, data-binding… • C: flex, extendable, suit for big project
Backbone, Spine…• P: Present Model -> View, view no direct access to model, safer
then MVC Backbone , Angular…
• VM: VM & V more tightly, suit small project Knockout…
TODO MVC http://todomvc.com/
Namespace• Automating nested namespacing
var application = { utilities:{ drawing:{ canvas:{ 2d:{} } } }};
// automatic namespacingvar myApp = myApp || {};
function extend( ns, ns_string ) { var parts = ns_string.split('.'), parent = ns, pl, i; pl = parts.length; for (i = 0; i < pl; i++) { if (typeof parent[parts[i]] == 'undefined') { parent[parts[i]] = {}; } parent = parent[parts[i]]; } return parent;}
var mod = extend(myApp, 'myApp.modules.module2');
• Deep object extensionfunction extend(destination, source) { var toString = Object.prototype.toString, objTest = toString.call({}); for (var property in source) { if (source[property] && objTest == toString.call(source[property])) { destination[property] = destination[property] || {}; extend(destination[property], source[property]); } else { destination[property] = source[property]; } } return destination;};
Jquery.extend
Design pattern in JQuery core• Adapter pattern
.css({opacity:.5})• Façade pattern
.ajax()• Observe pattern
.on(event, cssSelector, func)• Iterator pattern
array is also object• Proxy pattern
.proxy(function(){this…}, thisObj) control before sth really loaded • Builder Pattern
$(“<a>a link</a>”).appendTo(…) xml initing
Modular JS
• AMD• CMD• Harmony
Script loaders
AMD• Asynchronous Module Definition, Commonjs• RequireJS• define(moduleID, [dependencies], definition function)• require([dependencies], function)
https://github.com/amdjs/amdjs-api/wiki/AMD
CMD
• require()• Exports
cmd: Common Module Definition
Sea.js
• Modules are singletons.• New free variables within the module scope should not be introduced.• Execution must be lazy, import can be later: define(dependency, …) vs.
define(function(require(…))• With some care, modules may have cyclic dependencies.
define(function(require, exports, module) { // The module code goes here });
http://wiki.commonjs.org/wiki/Modules/Wrappings
harmony
• Export• Import• Module