JavaScript/Notes/Factory

From Noisebridge
Jump to navigation Jump to search

Factory Aspect, Added to a Decorator

This class describes generic concepts for creating and mixing design patterns. The basic principle for all design patterns is: Encapsulate the parts that vary.

The pattern itself is useful when you want to create at most one of an object for any associated element element, off a bubbled event.


Step 1: getById

The problem is finding a way to create a generic Factory that can be reused on various constructor functions for element Decorators.

Decorator Factory Aspect

A Decorator Factory Aspect is a Factory method, added as an Aspect to a constructor of a Decorator.

Before we add a Factory to a constructor function for an element decorator, Let's define Decorator (also called a wrapper), Factory and Aspect.

Decorator Pattern
Makes it possible to extend (decorate) the functionality of a class by wrapping an object. (Wikipedia link)
Factory Pattern
The Factory pattern is a creational design pattern that encapsulates the processes of creating objects (Wikipedia link)
Aspect
introduces separation of concern(s), as modularization of functionality (Wikipedia link)

Decorator Examples

Decorator is very common in JavaScript. For example: YAHOO.util.Element decorates an element, jQuery decorates a collection of elements.

Factory Example

The Factory gets or creates a decorated element. The id of the wrapper is the same as the id of the element. This is the part I want to make reusable:

<source lang="javascript">/**

* @constructor
* @param {String} id - the id of the element and widget. 
*/

function ElementWrapper(id, x) {

 this.id = id;
 this.x = x;

}

// Factory. // TODO: How can I make this generic/reusable? ElementWrapper.instances = {}; ElementWrapper.getById = function(id, x) {

 if(this.instances.hasOwnProperty(id)) return this.instances[id];
 return this.instances[id] = new this(id, x);

};

ElementWrapper.prototype = {

 show : function() { 
   document.getElementById(this.id).style.visibility = "visible";
 }

}; </source>

Benefits

Solves the problem of creating only one decorator per element id.

By calling getElementById, the decorator can avoid some of the problems with changing node references with innerHTML (though state changes must still be managed manually).

Problem: DRY

Don't Repeat Yourself

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

It is cumbersome and error-prone to write out a Factory each time. Since this is an idiom I use a lot, it makes sense to make it reusable.

I want to have a generic getById method that can be reused and will return an instance of the constructor that it is called on. I want to be able to pass extra arguments to that constructor (varargs).

Problem: Publicly Exposed Constructor

A factory should not expose the constructor, but should hide it so that the factory method must be used. I will explain the solution to this problem in part II.

Encapsulate the Parts That Vary

What varies?

The id parameter variable of getById does not change; it will always be present in any generic Factory. The parts of the Factory that vary are: The additional zero or more arguments (varargs, this case, x), and the context, or thisArg.

Resolving the context arg is easy.

If I can solve passing varargs to a constructor in a generic context, it will be possible to create a generic Factory Aspect.

Function newApply

A way to call new with variable arguments would solve this problem. A new + apply() would provide the varargs functionality of apply, but passed to [[Construct]], not [[Call]].

The source code for newApply:

<source lang="javascript"> /**

* @param {Function} fun constructor to be invoked.
* @param {Array} args arguments to pass to the constructor.
* Instantiates a constructor and uses apply().
*/

function newApply(fun, args) {

   if(arguments.length === 0) return;
   var f = arguments.callee, i;
   f.prototype = fun.prototype; // Add prototype.
   f.prototype.constructor = fun;
   i = new f;
   fun.apply(i, args);  // Apply the original constructor.
   return i;

} </source>

What's it Good For?

Now I can create the generic getById function I wanted. This function can be added as an aspect to any constructor function. Factory Aspect getById is a part of APE core.

<source lang="javascript"> getById : function(id) {

   if(!this.hasOwnProperty("instances")) this.instances = {};
   return this.instances[id] || (this.instances[id] = newApply(this, arguments));       

}, </source>

Using the Generic getById

This getById method can be used with ElementWrapper (above) or any other constructor that acts as a Decorator to an element and accepts the element's id as its first argument.

<source lang="javascript">Slider = function(id, dir) { /* ... */ };

// Factory. Slider.getById = getById; </source>

Then I can use:

<source lang="javascript">Slider.getById( "weight", 1 ); </source>

Subsequent calls to:

<source lang="javascript">Slider.getById( "weight" ); </source>

— will return the same Slider instance.

More Examples

I have used this approach for many widgets, including Calendar, Slider and Autocomplete (can't run off Github pages because there is no server side processing). The pattern is useful for building widgets that can be initialized lazily, on a bubbled event.

Reusable Concept

Another closely related technique is Decorator that accepts an element instead of an element's id. This is covered by getByNode.


Reflection

In most patterns, encapsulating the parts that vary entails creating an class. However, in JavaScript, this particular pattern was simple to implement by using just two functions (getById) and leveraging the dynamic nature of JavaScript.

Orthogonality and the DRY Principle, A Conversation with Andy Hunt and Dave Thomas, Part II by Bill Venners March 10, 2003

Step 2: Creating an Abstract Factory

The Factory can be made more abstract.

Be Smart, not Clever

By using a config parameter object, we can avoid the need for the clever newApply trick.

Problem: Publicly Exposed Constructor

Hide the constructor in a closure.

<source lang="javascript"> var ElementWrapper = (function(){

 function ElementWrapper(id, x) {
   this.id = id;
   this.x = x;
   this.show = _show;
 }
 function _show() { 
   document.getElementById(this.id).style.visibility = "visible";
 }
 var instances = {};
 return { 
   getById : function(id, x) {
     if(instances.hasOwnProperty(id)) return instances[id];
     return instances[id] = new ElementWrapper(id, x);
   }
 };

})(); </source>

Problem: Not Dry (reprise)

The Factory Method

<source lang="javascript"> function Factory(getConstructor){

 var i = 0, ctor;
 this.getById = getById;
 function getById(id, config) {
   var instances = this.instances;
     if(!instances) { // First time.
       instances = this.instances = {};
     // Get the constructor.
       ctor = getConstructor(this);
     }
     return instances[id] || (instances[id] = new ctor(id, config));
   }
 }

</source>

Implementation

<source lang="javascript"> var ElementWrapper = new Factory(function() {

 var defaultConfig = { 
   title : "Hella",
 };
 function ElementWrapperC(id, config) {
   config = config || defaultConfig;
   this.id = id;
   this.title = config.title;
   initEvents(this);
 }
 function initEvents(ew) {
   document.getElementById(ew.id).onclick = showLog;
 }
 function showLog(ev) {
   console.log(ev);
 };
 return ElementWrapperC;

}); </source>

Usage

<source lang="javascript"> ElementWrapper.getById("globalWrapper").title </source>

Source Code

[https://github.com/GarrettS/ape-javascript-library/blob/master/src/APE.js#L130 https://github.com/GarrettS/ape-javascript-library/blob/master/src/APE.js#L130]