Design patterns in javascript

Post on 01-Nov-2014

487 views 0 download

Tags:

description

 

transcript

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