<Learning JavaScript Design Patterns>

来源:互联网 发布:如何将mac 设置成中文 编辑:程序博客网 时间:2024/06/06 01:20

CHAPTER 8: Design Pattern Categorization




Creational

Based on the concept of creating an object.Class
Factory MethodThis makes an instance of several derived classes based on interfaced data or events.Object
Abstract FactoryCreates an instance of several families of classes without detailing concrete classes.BuilderSeparates object construction from its representation, always creates the same type of object.PrototypeA fully initialized instance used for copying or cloning.SingletonA class with only a single instance with global access points.

StructuralBased on the idea of building blocks of objectsClass
AdapterMatch interfaces of different classes therefore classes can work together despite incompatible interfacesObject
AdapterMatch interfaces of different classes therefore classes can work together despite incompatible interfacesBridgeSeparates an object's interface from its implementation so the two can vary independentlyCompositeA structure of simple and composite objects which makes the total object more than just the sum of its parts.DecoratorDynamically add alternate processing to objects.FacadeA single class that hides the complexity of an entire subsystem.FlyweightA fine-grained instance used for efficient sharing of information that is contained elsewhere.ProxyA place holder object representing the true object.

BehavioralBased on the way objects play and work together.Class
InterpreterA way to include language elements in an application to match the grammar of the intended language.Template MethodCreates the shell of an algorithm in a method, then defer the exact steps to a subclass.Object
Chain of ResponsibilityA way of passing a request between a chain of objects to find the object that can handle the request.
CommandEncapsulate a command request as an object to enable, logging and/or queuing of requests, and provides error-handling for unhandled requests.
IteratorSequentially access the elements of a collection without knowing the inner workings of the collection.
MediatorDefines simplified communication between classes to prevent a group of classes from referring explicitly to each other.
MementoCapture an object's internal state to be able to restore it later.
Observer
A way of notifying change to a number of classes to ensure consistency between the classes.
StateAlter an object's behavior when its state changes
StrategyEncapsulates an algorithm inside a class separating the selection from the implementation.
VisitorAdds a new operation to a class without changing the class.





CHAPTER 11: MV* Patterns


Templating


It has long been considered (and proven) a performance bad practice to manually create large blocks of HTML markup in-memory through string concatenation. Developers doing so have fallen prey to inperformantly iterating through their data, wrapping it in nested divs and using outdated techniques such as document.write to inject the 'template' into the DOM. As this typically means keeping scripted markup inline with your standard markup, it can quickly become both difficult to read and more importantly, maintain such disasters, especially when building non-trivially sized applications.


JavaScript templating solutions (such as Handlebars.js and Mustache) are often used to define templates for views as markup (either stored externally or within script tags with a custom type - e.g text/template) containing template variables. Variables may be deliminated using a variable syntax (e.g {{name}}) and frameworks are typically smart enough to accept data in a JSON form (of which model instances can be converted to) such that we only need be concerned with maintaining clean models and clean templates. Most of the grunt work to do with population is taken care of by the framework itself. This has a large number of benefits, particularly when opting to store templates externally as this can give way to templates being dynamically loaded on an asneeded basis when it comes to building larger applications.


Namespacing Fundamentals


Some topics in this section obviously overlaps each other, like that I cannot tell the difference between single global variable and module pattern, and their implementation relys on IIFE.


Single global variables

var myApplication = (function(){function(){//...},return{//...}})();

Object literal notation

var myApplication = {getInfo:function(){ /**/ },// we can also populate object literal to support// further object namespaces containing anything really:models : {},views : {pages : {}},collections : {}};


And the good thing is you can modify it later on:

myApplication.foo = function(){ /**/ }myApplication.utils = {toString:function(){//...},export: function(){//...}}


The different ways in which you can check to see if a variable (object namespace) already exists before defining it. You'll commonly see developers using Option 1, however Options 3 and 5 may be considered more thorough and Option 4 is considered a good best-practice. 

// This doesn't check for existence of 'myApplication' in// the global namespace. Bad practice as you can easily// clobber an existing variable/namespace with the same namevar myApplication = {};// The following options *do* check for variable/namespace existence.// If already defined, we use that instance, otherwise we assign a new// object literal to myApplication.//// 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;//


In addition to namespacing, it's often of benefit to decouple the default configuration for your application into a single area that can be easily modified without the need to search through your entire codebase just to alter them - object literals work great for this purpose.


Nested namespacing

A sample implementation of nested namespacing may look like this:

var myApp = myApp || {};// perform a similar existence check when defining nested// childrenmyApp.routers = myApp.routers || {};myApp.model = myApp.model || {};myApp.model.special = myApp.model.special || {};// nested namespaces can be as complex as required:// myApp.utilities.charting.html5.plotGraph(/*..*/);// myApp.modules.financePlanner.getSummary();// myApp.services.social.facebook.realtimeStream.getLatest();


You can also opt to declare new nested namespaces/properties as indexed properties as follows:

myApp["routers"] = myApp["routers"] || {};myApp["models"] = myApp["models"] || {};myApp["controllers"] = myApp["controllers"] || {};


This can mean an increased amount of work to perform lookups, however developers such as Juriy Zaytsev have previously tested and foundthe performance differences between single object namespacing vs the 'nested' approach to be quite negligible.


Immediately-invoked Function Expressions (IIFE)s

The simplest version of an IIFE:

// an (anonymous) immediately-invoked function expression(function(){ /*...*/})();// a named immediately-invoked function expression(function foobar(){ /*..*/}());// this is technically a self-executing function which is quite differentfunction foobar(){ foobar(); }


IIFE being applied to extend namespace:

var namespace = namespace || {};// here a namespace object is passed as a function// parameter, where we assign public methods and// properties to it(function( o ){o.foo = "foo";o.bar = function(){return "bar";};})(namespace);


More over, being used to encapsulate:

// namespace (our namespace name) and undefined are passed here// to ensure 1. namespace can be modified locally and isn't// overwritten outside of our function context// 2. the value of undefined is guaranteed as being truly// undefined. This is to avoid issues with undefined being// mutable pre-ES5.;(function ( namespace, undefined ) {// private propertiesvar foo = "foo",bar = "bar";// public methods and propertiesnamespace.foobar = "foobar";namespace.sayHello = function () {speak("hello world");};// private methodfunction speak(msg) {console.log("You said: " + msg);};// check to evaluate whether 'namespace' exists in the// global namespace - if not, assign window.namespace an// object literal}(window.namespace = window.namespace || {});// we can then test our properties and methods as follows// publicconsole.log(namespace.foobar); // foobarnamescpace.sayHello(); // hello world// assigning new propertiesnamespace.foobar2 = "foobar";console.log(namespace.foobar2);


Or, there is another alternative:

// let's extend the namespace with new functionality(function( namespace, undefined ){// public methodnamespace.sayGoodbye = function(){console.log(namespace.foo);console.log(namespace.bar);speak('goodbye');}}( window.namespace = window.namespace || {});


Namespace injection

Namespace injection is another variation on the IIFE where we 'inject' the methods and properties for a specific namespace from within a function wrapper using this as a namespace proxy.

The benefit this pattern offers is easy application of functional behaviour to multiple objects or namespaces and can come in useful when applying a set of base methods to be built on later (e.g. getters and setters).

var myApp = myApp || {};myApp.utils = {};(function() {var val = 5;this.getValue = function() {return val;};this.setValue = function(newVal) {val = newVal;}// also introduce a new sub-namespacethis.tools = {};}).apply(myApp.utils);// inject new behaviour into the tools namespace// which we defined via the utilities module(function(){this.diagnose = function(){return 'diagnosis';}}).apply(myApp.utils.tools);// note, this same approach to extension could be applied// to a regular IIFE, by just passing in the context as// an argument and modifying the context rather than just// 'this'// testingconsole.log(myApp); //the now populated namespaceconsole.log(myApp.utils.getValue()); // test getmyApp.utils.setValue(25); // test setconsole.log(myApp.utils.getValue());console.log(myApp.utils.tools.diagnose());


Its analogue of Call: 

// define a namespace we can use latervar ns = ns || {}, ns2 = ns2 || {};// the module/namespace creatorvar creator = function(val){var val = val || 0;this.next = function(){return val++};this.reset = function(){val = 0;}}creator.call(ns);// ns.next, ns.reset now existcreator.call(ns2, 5000);// ns2 contains the same methods// but has an overridden value for val of 5000



Advanced Namespacing


Automating nested namespacing


This approach was brought to more cleverly handle deeply extending namespace rather than with object literal:


var application = {utilities:{drawing:{canvas:{2d:{//...                   }}}}};


The trick is to define a function to handle the dirty work:


// top-level namespace being assigned an object literal var myApp = myApp || {}; // a convenience function for parsing string namespaces and // automatically generating nested namespaces function extend( ns, ns_string ) {var parts = ns_string.split('.'),parent = ns,pl, i;pl = parts.length;for (i = 0; i < pl; i++) {// create a property if it doesnt existif (typeof parent[parts[i]] == 'undefined') {parent[parts[i]] = {};}parent = parent[parts[i]];}return parent;} // sample usage: // extend myApp with a deeply nested namespace var mod = extend(myApp, 'myApp.modules.module2'); // the correct object with nested depths is output console.log(mod); // minor test to check the instance of mod can also // be used outside of the myApp namesapce as a clone // that includes the extensions console.log(mod == myApp.modules.module2); //true // further demonstration of easier nested namespace // assignment using extend extend(myApp, 'moduleA.moduleB.moduleC.moduleD'); extend(myApp, 'longer.version.looks.like.this'); console.log(myApp); 


Dependency declaration pattern

If each time we refer to a variable inside a namespace (usually a deeply nested one), we use its full qualified name, that would be much too verbose and inefficient, and to solve this, you candeclare a local variable referencing those variables, as a shortcut. The author suggests using this pattern only at modular level, rather than function level. 


// common approach to accessing nested namespaces myApp.utilities.math.fibonacci(25); myApp.utilities.math.sin(56); myApp.utilities.drawing.plot(98,50,60); // with local/cached references var utils = myApp.utilities, maths = utils.math, drawing = utils.drawing; // easier to access the namespace maths.fibonacci(25); maths.sin(56); drawing.plot(98, 50,60); // note that the above is particularly performant when// compared to hundreds or thousands of calls to nested // namespaces vs. a local reference to the namespace


Deep Object Extension

This pattern is similar to 'automating namespace', except it merges the nested sub-namespaces as deeply as possible.


// extend.js // written by andrew dupont, optimized by addy osmani function 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; }; console.group("objExtend namespacing tests"); // define a top-level namespace for usagevar myNS = myNS || {}; // 1. extend namespace with a 'utils' object extend(myNS, {utils:{} }); console.log('test 1', myNS); //myNS.utils now exists // 2. extend with multiple depths (namespace.hello.world.wave) extend(myNS, {hello:{world:{wave:{test: function(){//...}}}} }); // test direct assignment works as expected myNS.hello.test1 = 'this is a test'; myNS.hello.world.test2 = 'this is another test'; console.log('test 2', myNS); // 3. what if myNS already contains the namespace being added // (e.g. 'library')? we want to ensure no namespaces are being // overwritten during extension myNS.library = {foo:function(){} }; extend(myNS, {library:{bar:function(){//...}} }); // confirmed that extend is operating safely (as expected) // myNS now also contains library.foo, library.bar console.log('test 3', myNS); // 4. what if we wanted easier access to a specific namespace without having // to type the whole namespace out each time?. var shorterAccess1 = myNS.hello.world; shorterAccess1.test3 = "hello again";console.log('test 4', myNS); //success, myApp.hello.world.test3 is now 'hello again' console.groupEnd();



0 0
原创粉丝点击