rsurjano
8/18/2016 - 4:43 PM

JavaScript Patterns

JavaScript Patterns

// Visitor lets you define a new operation without changing 
// the class of the elements on which it operates.


// Define 'Employee' Class
function Employee(name, salary, vacation) {
	this.name = name;
	this.salary = salary;
	this.vacation = vacation;
}
Employee.prototype = {
	getName: function() {
		return this.name;
	},
	getSalary: function() {
		return this.salary;
	},
	setSalary: function(newSalary) {
		this.salary = newSalary;
	},
	getVacation: function() {
		return this.vacation;
	},
	setVacation: function(vacation_days) {
		this.vacation = vacation_days;
	},		

	// Let visitor apply its functionallity
	// on the instance, by passing the instance
	// object to the 'visit' function.
	accept: function(visitor) {
		visitor.visit(this);
	}
}

// Define Visitors
function RaiseSalary() {} // Salaray raise visitor
RaiseSalary.prototype.visit = function(employee) {
	employee.setSalary(employee.getSalary() * 1.2);
}
function AddVacationDays() {} // Vacation days addition visitor
AddVacationDays.prototype.visit = function(employee) {
	employee.setVacation(employee.getVacation() + 3);
}


/****************
  Usage Example
 ***************/
 // data logger
 function logData(employees, msg) {
 	console.log(msg);
 	console.log('--------------------------');
 	employees.forEach(function(employee) {
		console.log('Name: ' + employee.getName());
		console.log('Salary: ' + employee.getSalary());
		console.log('Vacation Days: ' + employee.getVacation());
		console.log('---');
	});
	console.log('\n\n\n');
 }
 // set employees
var employees = [
	new Employee('Guy', 17000, 10),
	new Employee('Dor', 8000, 7),
	new Employee('Josh', 11000, 12),
]; 
// initialize visitors
var salaryVisitor = new RaiseSalary();
var vacationVisitor = new AddVacationDays();

// log data before accepting visitors
logData(employees, 'Before accepting visitors:');

// apply visitors functionallity
employees.forEach(function(employee) {
	employee.accept(salaryVisitor);
	employee.accept(vacationVisitor);
});

// log data to see the changes after accepting visitors
logData(employees, 'After accepting visitors:');

//Structure
var mySingleton = (function() {
  
  function init(options) {
    //some private variables
    var x = '1', y = 2, z = 'Abc', pi = Math.PI;
    //return public methods(accessing private variables if needed.)
    return {
      X : x,
      getPi : function() {
        return pi;
      }
    }
  }
  //There must be exactly one instance of a class, 
  //and it must be accessible to clients from a well-known access point
  var instanceOfSingleton;
  return {
    initialize: function(options) {
      //initialize only if not initialized before
      if(instanceOfSingleton === undefined) {
        instanceOfSingleton = init(options);
      }
      return instanceOfSingleton;
    }
  };

})();

var singleton = mySingleton.initialize();
console.log(singleton.X); //'1'
console.log(singleton.getPi());//3.141592653589793



 // setTimeout() inside a loop.
  for (var i = 1; i <= 3; i++) {
    setTimeout(function () {
      console.log(i);     // prints 4 4 4
    }, 1000);
  }

  // Work all fine if we use `let` keyword in ES6
  for (let i = 1; i <= 3; i++) {
    setTimeout(function () {
      console.log(i);     // prints 1 2 3
    }, 1000);
  }

  // Locking the looped values inside a IIFE (closure).
  for (var i = 1; i <= 3; i++) {
    (function (index) {
      setTimeout(function () {
        console.log(index);     // prints 1 2 3
      }, 1000);
    })(i);
  }

  // Note: When the IIFE is inside the setTimeout, it prints the corrct values.
  // However, the values are printed immediately and not after the timout value.
  // Essentially rendering the setTimeout useless.
  // setTimeout() needs a fn as it's 1st parameter.
  for (var i = 1; i <= 3; i++) {
    setTimeout((function (index) {
      console.log(index);         // prints 1 2 3
    })(i), 1000);
  }

  // You can still use and IIFE inside setTimeout(), but you need to return a function as it's first parameter.
  for (var i = 1; i <= 3; i++) {
    setTimeout((function (index) {
      return function () {
        console.log(index);     // prints 1 2 3
      };  // IIFE needs to return a function that setTimeout can schedule.
    })(i), 1000);
  }

  // Note: Both setTimeout and setInterval accept and additional params that can be passed to the callback fn.
  // Thanks: https://twitter.com/WebReflection/status/701091345679708161
  for (var i = 0; i < 10; i++) {
    setTimeout(function (i) {
      console.log(i);
      // This will print 0 1 2 3 4 5 6 7 8 9
    }, 1000, i)
  }

  // Another way is to just create a separate function.
  for (var i = 0; i < 10; i++) {
    registerTimeout(i);
  }
  function registerTimeout (i) {
    setTimeout(function () {
      console.log(i);
      // This will print 0 1 2 3 4 5 6 7 8 9
    }, 1000);
  }
// You should use proxy pattern when you want to extend a
// class functionality without changing its implementation

/****************
  Original Class
 ****************/
function Hotel(stars, isCityCenter, isNew, numberOfRooms, avgRoomSize) {
	this.stars = stars;
	this.isCityCenter = isCityCenter;
	this.isNew = isNew;
	this.numberOfRooms = numberOfRooms;
	this.avgRoomSize = avgRoomSize;
}
Hotel.prototype = { // Define getters and setters
	getStars: function() {
		return this.stars;
	},
	setStars: function(starsRate) {
		this.stars = starsRate;
	},
	isCityCenter: function() {
		return this.isCityCenter;
	},
	toggleCenter: function() {
		this.isCityCenter = !this.isCityCenter;
	},
	isNew: function() {
		return this.isNew;
	},
	toggleNew: function() {
		this.isNew = !this.isNew;
	},
	getNumberOfRooms: function() {
		return this.numberOfRooms;
	},
	setNumberOfRooms: function(num) {
		this.numberOfRooms = num;	
	},
	getAvgRoomSize: function() {
		return this.avgRoomSize;
	},
	setAvgRoomSize: function(newAvg) {
		this.avgRoomSize = newAvg;
	}
}


/****************
      Proxy
 ****************/
var HotelProxy = function(stars, isCityCenter, isNew, numberOfRooms, avgRoomSize) {
	// Create Hotel Instance
	var hotel = new Hotel(stars, isCityCenter, isNew, numberOfRooms, avgRoomSize);
	// Private function
	function scoreByStars(stars) {
		switch(stars) {
			case(5):
				return 6;
			case(4):
				return 5;
			case(3): 
				return 3;
			case(2):
				return 1.5;
			default:
				return 0.5;
		}
	}
	// Extend hotel instance
	Object.assign(hotel, {
		getScore: function() {
			var score = scoreByStars(hotel.stars);
			if(hotel.isCityCenter) {
				score += 2;
			}
			if(hotel.isNew) {
				score += 1.5;
			}
			if(hotel.numberOfRooms > 5000) {
				score += 0.5;
			}
			return score;
		},
		getHotelRoomsVolume: function() {
			return hotel.numberOfRooms * hotel.avgRoomSize;
		}
	});
	
	// Return extended instance
	return hotel;
}

// Usage example
var hp = HotelProxy(4, true, false, 2800, 150);
console.log(hp.getScore()); // 7
console.log(hp.getHotelRoomsVolume()); // 420000
hp.setAvgRoomSize(160);
console.log(hp.getHotelRoomsVolume()); // 448000
hp.setStars(5);
console.log(hp.getScore()); //8

//Structure
var obj = {
  name: "My object's name.", 
  objFunc: function () {
    console.log( "Yay! A function!" );
  }
};

//set a new object/function's prototype as another existing one
//here newObj is created with prototype as obj.
var newObj = Object.create( obj );
newObj.gender = 'Female'; 
// Now we can see that one is a prototype of the other
console.log( newObj.name ); //My object's name.
console.log( newObj.gender ); //Female


// OLOO (objects linked to other objects) pattern explored

// CONSTRUCTOR SYNTAX VS OLOO
// Constructor form
function Foo() {
}
Foo.prototype.y = 11;

function Bar() {
}
// Object.create(proto[, propertiesObject]) method creates a new object with the specified prototype object and properties.
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.z = 31;

var x = new Bar();
console.log(x.y + x.z);  // 42


// OLOO form
var FooObj = {y: 11};

var BarObj = Object.create(FooObj);
BarObj.z = 31;

var x = Object.create(BarObj);
console.log(x.y + x.z);  // 42


/**
 * CLASS SYNTAX VS OLOO
 */
// ES6 Class style
class Foo {
  constructor(x, y, z) {
    // Object.assign(target, ...sources) method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.
    Object.assign(this, {x, y, z});
  }

  hello() {
    console.log(this.x + this.y + this.z);
  }
}

var instances = [];
for (var i = 0; i < 500; i++) {
  instances.push(
    new Foo(i, i * 2, i * 3)
  );
}
instances[37].hello(); // 222


// OLOO Form
function Foo(x, y, z) {
  return {
    hello() {
      console.log(this.x + this.y + this.z);
    },
    x,
    y,
    z
  };
}

var instances = [];

for (var i = 0; i < 500; i++) {
  instances.push(
    Foo(i, i * 2, i * 3)
  );
}
instances[37].hello();  // 222
function theSubject(){
  this.handlerList = [];
}

theSubject.prototype = {
  addObserver: function(obs) {
    //add all the observers in an array.
    this.handlerList.push(obs);
    console.log('added observer', this.handlerList);
  },
  removeObserver: function(obs) {
    //remove given observer from the array.
    for ( var ii=0, length = this.handlerList.length; ii<length; ii++ ) {
      if(this.handlerList[ii] === obs) {
          this.handlerList.splice(ii,1);
          console.log('removed observer', this.handlerList);
      }
    }
  },
  notify: function(obs, context) {
    //for all functions in handler, notify
    var bindingContext = context || window;
    this.handlerList.forEach(function(fn){
        fn.call(bindingContext, obs);
    });
  }
}

function init() {
    var theEventHandler = function(item) { 
        console.log("fired: " + item); 
    };
 
    var subject = new theSubject();
 
    subject.addObserver(theEventHandler); //adds the given function in handler list
    subject.notify('event #1'); //calls the function once.
    subject.removeObserver(theEventHandler); //removes this function from the function list
    subject.notify('event #2'); //notify doesn't call anything
    subject.addObserver(theEventHandler); //adds the function again
    subject.notify('event #3'); //calls it once with event 3
}

init();
// Perhaps the most used pattern in javascript web development.
// MVC(Model-View-Controller) seperates data model representation 
// from data visuallization.
// The glue that connects the model changes and view updates
// is the contoller.


// Define Account Model
function AccountModel(owner, balance) {
	this.owner = owner;
	this.balance = balance;
}
AccountModel.prototype = {
	getOwner: function() {
		return this.owner;
	},
	getBalance: function() {
		return this.balance;
	}
}

// Define Account View
function AccountView() {}
AccountView.prototype.showAccountDetails = function(owner, balance) {
	console.log('Account owner: ' + owner);
	console.log('Account balance: ' + balance);
}

// Define Account Controller
function AccountController(model, view) {
	this.accountView = function() {
		view.showAccountDetails(model.getOwner(), model.getBalance());
	}
	this.deposit = function(amount) {
		this.setBalance(model.getBalance() + amount);
		this.accountView();
	}
	this.discount = function(amount) {
		this.setBalance(model.getBalance() - amount);
		this.accountView();
	}
	this.setOwner = function(newOwner) {
		model.owner = newOwner;
		this.accountView();
	}
	this.setBalance = function(newBalance) {
		model.balance = newBalance;
		this.accountView();
	}
}

// Usage Example
var controller = new AccountController(new AccountModel('Ben', 15000), new AccountView());
controller.accountView();
// Account owner: Ben
// Account balance: 15000
controller.deposit(500);
// Account owner: Ben
// Account balance: 15500
controller.discount(120);
// Account owner: Ben
// Account balance: 15380
controller.setOwner('Bob');
// Account owner: Bob
// Account balance: 15380
controller.setBalance(0);
// Account owner: Bob
// Account balance: 0
controller.deposit(1000000);
// Account owner: Bob
// Account balance: 1000000

//Structure
var a = (function(){
  //private variables & methods
  var privateVar = 'X';
  //public methods and variables
  return {
    getX: function(){
      return privateVar;
    }
  }
})();

a.getX(); //X


//Structure and Example
var mixins = {
  get: function(){
    console.log( "get this item" );
  },
  set: function(){
    console.log( "set this item" );
  },
  delete: function(){
    console.log( "delete it right away!" );
  }
};
 
// Another skeleton constructor
function aConstructor(){
  this.thatIsAllFolks = function(){ 
    console.log('Nope! I refuse to do anything anymore!!!')
  };
}
 
// Extend both protoype with our Mixin
for(var key in mixins) aConstructor.prototype[key] = mixins[key];
 
// Create a new instance of aConstructor
var myMixinConstructor = new aConstructor();

myMixinConstructor.get(); //get this item
myMixinConstructor.thatIsAllFolks(); //Nope! I refuse to do anything anymore!!!

// Memento pattern is used to restore state of an object to a previous state. 
function Memento(initialState) {
	var state = initialState || null;
	var stateList = state ? [state] : [];

	return {
		getState: function() {
			return state;
		},
		getStateList: function() {
			return stateList;
		},
		get: function(index) {
			if(index > -1 && index < stateList.length) {
				return stateList[index];
			}
			else {
				throw new Error('No state indexed ' + index);
			}
		},
		addState: function(newState) {
			if(!newState)
				throw new Error('Please provide a state object');
			state = newState;
			stateList.push(newState);
		}
	} 
}

// Helper function used to deep copy an object using jQuery
function copy(obj) {
	return jQuery.extend(true, {}, obj);
}

// Example Usage, please notice that using this pattern, you should
// not mutate objects or arrays, but clone them, since they are passed
// by reference in javascript.
// If your state is a string or a number however, you may mutate it.
var songs = {
	Queen: ['I want to break free', 'Another on bites the dust', 'We will rock you'],
	Scorpins: ['Still loving you', 'Love will keep us alive', 'Wind of change'],
	Muse: ['Butterflies and hurricanes', 'Starlight', 'Unintended'],
	BeeGees: ['How deep is your love', 'Staying alive']
}

var memento = Memento(copy(songs)); // Initialize Memento
songs.BeeGees.push('Too much heaven');
songs.Muse.push('Hysteria');
memento.addState(copy(songs)); // Add new state to memento
songs['Abba'] = ['Mama mia', 'Happy new year'];
songs['Eric Clapton'] = ['Tears in heaven', 'Bell bottom blues'];
memento.addState(copy(songs)); // Add new state to memento
console.log(memento.getStateList()); // log state list
console.log(memento.getState()); // log current state
console.log(memento.get(1)); // log second state
songs = memento.get(0); // set songs to initial state
memento.addState(copy(songs)); // Add new old state to memento
console.log(memento.getStateList()); // log state list

// Mediator pattern is used to reduce communication 
// complexity between multiple objects or classes.
function Mediator() {
	var users = [];

	return {
		addUser: function(user) {
			users.push(user);
		},
		// Message sending business logic
		publishMessage: function(msg, receiver) {
			if(receiver) {
				receiver.messages.push(msg);
			}
			else {
				users.forEach(function(user) {
					user.messages.push(msg);
				});				
			}
		}
	}
}

// Usage Example
var mediator = Mediator(); // Initialize mediator
// Define user class
function User(name) {
	this.name = name;
	this.messages = [];
	mediator.addUser(this);
}
User.prototype.sendMessage = function(msg, receiver) {
	msg = '[' + this.name + ']: ' + msg;
	mediator.publishMessage(msg,receiver);
}

// Initialize users
var u1 = new User('Donald');
var u2 = new User('Peter');
var u3 = new User('Anna'); 
// Message sending
u1.sendMessage('Hi, anybody here?');
u2.sendMessage('Hi Donald, nice to meet you.', u1);
u3.sendMessage('Hi Guys!');

// Access elements of a collection sequentially without
// needing to know the underlying representation.

/************
   Iterator
 ************/
function Iterator(arr) {
  var currentPosition = -1;
  
  return {
     hasNext:function() {
       return currentPosition+1 < arr.length;
     },
     next: function() {
        if(!this.hasNext())
           return null;
        currentPosition++;
        return arr[currentPosition];
     }
  }
}

// Example Usage
var people = [{id:1,name:'John'}, {id:2,name:'George'}, {id:3,name:'Guy'}];
var peopleIterator = Iterator(people); // Create Iterator for 'people'
while(peopleIterator.hasNext()) {
	var person = peopleIterator.next();
	console.log(person.name + '\'s id is: ' + person.id + '!');
}

// John's id is: 1!
// George's id is: 2!
// Guy's id is: 3!

//only common data here is model and brand, 
//and created a flyweight object, that saves memory
var Car = function(model, brand) {
  this.model = model;
  this.brand = brand;
}

//carFactory using the common car model/method
var carFactory = (function() {
  var existingCars = {}, existingCar;
  return {
    createCar: function(model, brand) {
      existingCar = existingCars[model];
      if (!!existingCar) {
        return existingCar;
      }
      var car = new Car(model, brand);
      existingCars[model] = car;
      return car;
    }
  }
})();

//carProductionManager using the common car model/method
var carProductionManager = (function() {
  var carDb = {};
  return {
    addCar: function(carId, model, brand, color, carType){
      var car = carFactory.createCar(model, brand);
      carDb[carId] = {
          color: color,
          type: carType,
          car: car
      }
    },
    repaintCar: function(carId, newColor) {
      var carData = carDb[carId];
      carData.color = newColor
    }
  }
})();

var fromPrototype = function(prototype, object) {
    var newObject = Object.create(prototype);
    for (var prop in object) {
        if (object.hasOwnProperty(prop)) {
            newObject[prop] = object[prop];
        }
    }
  return newObject;
};


// Define our `DeviceFactory` base object
var DeviceFactory = {
    screen: function() {
        return 'retina';
    },
    battery: function() {
        return 'lithium ion battery';
    },
    keypad: function() {
        return 'keyboard';
    },
    processor: function() {
        return 'Intel Core-i5';
    }
};

// Extend `DeviceFactory` with other implementations
DeviceFactory.makeLaptop = function() {
    return fromPrototype(DeviceFactory, {
        screen: function() {
            return 'retina 13 inches';
        },
        battery: function() {
            return 'lithium ion 9 hours battery';
        },
        keypad: function() {
            return 'backlit keyboard';
        },
        processor: function() {
            return 'Intel Core-i5'
        }
    });
};

DeviceFactory.makeSmartPhone = function() {
    return fromPrototype(DeviceFactory, {
        screen: function() {
            return 'retina 5 inches';
        },
        battery: function() {
            return 'lithium ion 15 hours';
        },
        keypad: function() {
            return 'touchscreen keypad';
        },
        processor: function() {
            return 'ARMv8'
        }
    });
};

DeviceFactory.makeTablet = function() {
    return fromPrototype(DeviceFactory, {
        screen: function() {
            return 'retina 9 inches';
        },
        battery: function() {
            return 'lithium ion 15 hours';
        },
        keypad: function() {
            return 'touchscreen keypad';
        },
        processor: function() {
            return 'ARMv8'
        }
    });
};


var appleMacbookPro = DeviceFactory.makeLaptop();
console.log(appleMacbookPro.screen()); // returns 'retina 13 inches';
var iPhoneSomeS = DeviceFactory.makeSmartPhone();
var iPadSomeSS = DeviceFactory.makeTablet();
//Structure and example
//Facading hides complexities from the user.
var mouse = (function() {
  var privates = {
    getActivity: function(act) {
      var activity = act.toLowerCase();
      if(activity === 'click') {
        return "User is clicking";
      } else if (activity === 'hover') {
        return "User is hovering";
      } else if (activity === 'rightclick') {
        return "User right clicked";
      } else if (activity === 'scroll') {
        return "User scrolled"
      } else {
        return "Unrecognised activity";
      }
    }
  }

  return {
    facade: function(activity) {
      return privates.getActivity(activity);
    }
  }
})();

console.log(mouse.facade('hover')); //User is hovering


//Structure
function functionA () {
  this.a = function() { return 'a'; }
}

function describeA ( anA ) {
  var aa = anA.a();

  anA.a = function () {
    return 'An "' + aa + '" is the first alphabet in English and the most important one.'
  }
}

var anA = new functionA();

describeA( anA );//here aa='a'

var output = anA.a();

console.log(output); //'An a is the first alphabet in English and the most important one.'
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
	var timeout;
	return function() {
		var context = this, args = arguments;
		var later = function() {
			timeout = null;
			if (!immediate) func.apply(context, args);
		};
		var callNow = immediate && !timeout;
		clearTimeout(timeout);
		timeout = setTimeout(later, wait);
		if (callNow) func.apply(context, args);
	};
};

// Usage
var myEfficientFn = debounce(function() {
	// All the taxing stuff you do
}, 250);
window.addEventListener('resize', myEfficientFn); 
//Structure
function aFunction(a,b) {
  this.a = a;
  this.b = b;
}

aFunction.prototype.protoFunction = function(){
  return this.a;
}

var aA = new aFunction('a','b');
console.log(aA.protoFunction());
//'a';
// in Javascript, is completely pointless.
// Structure and example
var commandPattern = (function(){
  var commandSet = {
    doSomething: function(arg1, arg2) {
      return "This is argument 1 "+ arg1 + "and this is arg 2 "+ arg2;
    },
    doSomethingElse: function(arg3) {
      return "This is arg 3 "+arg3;
    },
    executeCommands: function(name) {
      return commandSet[name] && commandSet[name].apply( commandSet, [].slice.call(arguments, 1) ); 
      //gives arguments list
    }
  };
  return commandSet;
})();
commandPattern.executeCommands( "doSomethingElse", "Ferrari");
commandPattern.executeCommands( "doSomething", "Ford Mondeo", "54323" );