JavaScript/Notes/Factory: Difference between revisions

From Noisebridge
Jump to navigation Jump to search
Garrett (talk | contribs)
No edit summary
 
(100 intermediate revisions by 4 users not shown)
Line 1: Line 1:
= Factory Aspect, Added to a Decorator =  
= Factory Aspect, Added to a Decorator =  
== Step 1: getById==
<p>
This 2-part class will be a soup-to-nuts explanation of widget design, a description the Factory pattern, a recapitulation of Event Bubbling strategy, and generic concepts for creating and mixing design patterns.
</p>
 
=== Prerequisite ===
<p>
You'll need to have a good understanding of [https://noisebridge.net/wiki/JavaScript/Notes/Function Functions and context], and [https://noisebridge.net/wiki/JavaScript/Notes/Scope Scope].


            <p>
Suggested review includes [https://noisebridge.net/wiki/JavaScript/Notes/Singleton Singleton] and the event bubbling section of [https://noisebridge.net/wiki/JavaScript/Notes/ClassnameSwap#Lesson:_DOM_Event_Flow ClassName Swap] lesson.
    This class describes generic concepts for creating and mixing design patterns.
</p>
 
=== Object Oriented Design ===
<p>
     The basic principle for all design patterns is: <em>Encapsulate the parts that vary</em>.  
     The basic principle for all design patterns is: <em>Encapsulate the parts that vary</em>.  
</p>
</p>
[http://garretts.github.io/ape-javascript-library/example/anim/seekTo.html Animation Transition]
Pseudocode:
<pre>
color:      rgb(12, 44, 89)  -->  rgb(233, 197, 17);
marginLeft: 0                -->  -9px;
padding:    0                -->  12px;
zIndex:    1                -->  100
</pre>
Each animation's time duration is passed to it when it is called. The style values are supplied by the caller.
The part that varies is the Style Value Type, and that depends on the Style Property.


[http://garretts.github.io/ape-javascript-library/build/anim/StyleTransition.js StyleTransition.js]
==== Decorator ====
<p>
<p>
    The problem is finding a way to create a generic <dfn>Factory</dfn> that can be reused on
Factory is useful when you want to create at most one of an object for any
    various constructor functions for element <dfn>Decorators</dfn>.  
associated element, off a bubbled event, using an object pool to "get or create" a wrapper, or <dfn>Decorator</dfn>.
</p>
 
== Step 1: getById ==
<source lang=javascript>
function ElementWrapper(id, x) {
  this.id = id;
  this.x = x;
}
 
ElementWrapper.instances = {};
 
ElementWrapper.getById = function(id, x) {
  if(ElementWrapper.instances.hasOwnProperty(id)) return ElementWrapper.instances[id];
  return ElementWrapper.instances[id] = new ElementWrapper(id, x);
};
</source>
 
==== Problems ====
'''Problem:''' Not DRY. Cannot be arbitrarily reused other constructor functions.
'''Problem:''' <code>ArgumentList</code> ([http://www.ecma-international.org/ecma-262/5.1/#sec-11.2 &sect;11.2]) is set to two.
'''Problem:''' Encapsulation. The factory and the constructor are exposed publicly.</p>


=== Decorator Factory Aspect ===  
=== Decorator Factory Aspect ===  
Line 21: Line 65:


; Decorator Pattern
; Decorator Pattern
: makes it possible to extend (decorate) the functionality of a class by adding a new decorator class that wraps the original class. ([http://en.wikipedia.org/wiki/Decorator_pattern Wikipedia link])
: Add functionality to an object by wrapping it. ([http://en.wikipedia.org/wiki/Decorator_pattern Wikipedia link])
      
      
; Factory Pattern
; Factory Pattern
: The Factory pattern is a creational design pattern that encapsulates the processes of creating objects ([http://en.wikipedia.org/wiki/Factory_pattern Wikipedia link])
: The Factory pattern is a creational design pattern that encapsulates the processes of creating objects ([http://en.wikipedia.org/wiki/Factory_pattern Wikipedia link]). That way, we can create objects lazily and pool them in an object pool.


; Aspect  
; Aspect  
: <cite>introduces separation of concerns, specifically cross-cutting concerns, as an advance in modularization</cite> ([http://en.wikipedia.org/wiki/Aspect-oriented_programming Wikipedia link])
: <cite>introduces separation of concern(s), as modularization of functionality</cite> ([http://en.wikipedia.org/wiki/Aspect-oriented_programming Wikipedia link])


=== Decorator Examples ===
=== Decorator Examples ===
<p>
<p>
   <dfn>Decorator</dfn> is very common in JavaScript. For example: [http://developer.yahoo.com/yui/docs/Element.js.html YAHOO.util.Element] <dfn>decorates</dfn> an element,  
   <dfn>Decorator</dfn> is very common in JavaScript. For example: [http://developer.yahoo.com/yui/docs/Element.js.html YAHOO.util.Element] <dfn>decorates</dfn> an element,  
   jQuery <dfn>decorates</dfn> an array of elements.  
   jQuery <dfn>decorates</dfn> a collection of elements.  
</p>
</p>


Line 40: Line 84:
The <code>id</code> of the <dfn>wrapper</dfn> is the same as the <code>id</code> of the element.  
The <code>id</code> of the <dfn>wrapper</dfn> is the same as the <code>id</code> of the element.  
This is the part I want to make reusable:</p>
This is the part I want to make reusable:</p>
<source lang="javascript">/**
<source lang="javascript">/**
  * @constructor
  * @constructor
Line 100: Line 145:
<strong>What varies?</strong>  
<strong>What varies?</strong>  


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


    Resolving the context arg is easy.  
Resolving the context arg is easy.  


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


==== Function newApply ====  
==== Function newApply ====  
A way to call <code>new</code> with variable arguments would solve this problem.
A way to call <code>new</code> with variable arguments would solve this problem.
A <code>new + apply()</code> would provide the varargs functionality of <code>apply</code>,  
A <code>new + apply()</code> would provide the varargs functionality of <code>apply</code>,  
but passed to <code><nowiki>[[Construct]]</nowiki></code>, not <code><nowiki>[[Call]]</nowiki></code>.  
but passed to <code><nowiki>[[Construct]]</nowiki></code>, not <code><nowiki>[[Call]]</nowiki></code>. (see [http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.4.3 Function.prototype.apply], [http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.1 <code><nowiki>[[Call]]</nowiki></code>]).


The source code for <code>newApply</code>:
The source code for <code>newApply</code>:
Line 124: Line 169:
  * Instantiates a constructor and uses apply().
  * Instantiates a constructor and uses apply().
  */
  */
function F(){};
function newApply(fun, args) {
function newApply(fun, args) {
     if(arguments.length === 0) return;
     var i;
     var f = arguments.callee, i;
    F.prototype = fun.prototype; // Add prototype.
     F.prototype.constructor = fun;


    f.prototype = fun.prototype; // Add prototype.
     i = new F;
    f.prototype.constructor = fun;
 
     i = new f;
     fun.apply(i, args);  // Apply the original constructor.
     fun.apply(i, args);  // Apply the original constructor.
     return i;
     return i;
Line 137: Line 181:
</source>
</source>


==== What's it Good For? ====
<h4>Usage</h4>
  Now I can create the generic <code>getById</code> function I wanted. This function can be added as an <dfn>aspect</dfn>
  to any constructor function. <dfn>Factory</dfn> <dfn>Aspect</dfn> <code>getById</code> is a part of
  <code title="AOP Pointcuts for EcmaScript">APE</code> core.


Early versions of this method appeared in APE JavaScript library, which has been copied.
==== What's newApply Good For? ====
Generic <code>getById</code> function I wanted, to use <dfn>aspect</dfn> with any constructor function.
===== Library Aspect =====
<source lang="javascript">
<source lang="javascript">
getById : function(id) {
function getById(id) {
     if(!this.hasOwnProperty("instances")) this.instances = {};
     if(!this.hasOwnProperty("instances")) this.instances = {};
     return this.instances[id] || (this.instances[id] = newApply(this, arguments));       
     return this.instances[id] || (this.instances[id] = newApply(this, arguments));       
},
}
</source>
 
===== Implementation =====
<source lang="javascript">
function ElementWrapper(id, x, y) {
  this.id = id;
  this.x = x;
  this.y = y;
}
ElementWrapper.getById = getById;
</source>
 
===== Usage =====
<source lang="javascript">
ElementWrapper.getById("a", 2, 3).y; // 3
</source>
</source>
Example in jsbin: [http://jsbin.com/AgARAXI/1/embed?html,js,output live example].
==== Using the Generic <code>getById</code> ====
==== Using the Generic <code>getById</code> ====


<p>This <code>getById</code> method can be used with <code>ElementWrapper</code> (above)  
<p>This <code>getById</code> method used with <code>ElementWrapper</code> (above)  
or any other constructor that acts as a <dfn>Decorator</dfn> to an element and  
or any other constructor that acts as a <dfn>Decorator</dfn> to an element and  
accepts the element's <code>id</code> as its first argument.
accepts the element's <code>id</code> as its first argument. All other arguments will be passed to the constructor using <code>newApply</code>.
</p>
</p>
<source lang="javascript">Slider = function(id, dir) { /* ... */ };
<source lang="javascript">Slider = function(id, dir) { /* ... */ };
Line 174: Line 237:
=== More Examples ===  
=== More Examples ===  


<p>I have used this approach for many widgets, including  
<p>I have used a modified version of this approach for many widgets, including  
[http://garretts.github.io/ape-javascript-library/example/widget/calendar/ Calendar],
 
[http://garretts.github.io/ape-javascript-library/example/drag/Slider/ Slider] and
* [http://garretts.github.io/ape-javascript-library/example/widget/calendar/ Calendar],
Autocomplete (can't run off Github pages because there is no server side processing).  
* [http://garretts.github.io/ape-javascript-library/example/drag/Slider/ Slider]  
The pattern is useful for building widgets that can be initialized lazily, on a bubbled event.</p>
* [http://garretts.github.io/ape-javascript-library/example/widget/scroller/ Scroller]
 
Autocomplete (can't run off Github pages because there is no server side processing). This pattern is useful for building widgets that can be initialized lazily, on a bubbled event.</p>
 
(slider uses [https://noisebridge.net/wiki/JavaScript/Notes/Function#Function.prototype.call overflow hidden]).


=== Reusable Concept ===  
=== Reusable Concept ===  
Line 186: Line 253:




=== Reflection ===
 
<p>
<p>
In most patterns, encapsulating the parts that vary entails creating an class.  
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  
However, in JavaScript, this Factory pattern was simple to implement by using just  
two functions (<code>getById</code>) and leveraging the dynamic nature of  
two functions (<code>getById</code>) and leveraging the dynamic nature of  
JavaScript.
JavaScript.
Line 200: Line 267:
</p>
</p>


== Step 2: Creating an Abstract Factory==
== Step 2: Factory ==


=== Problem: Publicly Exposed Constructor ===
=== 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.
=== Solution: Hide the constructor in a closure. ===


<source lang="javascript">
<source lang="javascript">
Line 212: Line 279:
     this.id = id;
     this.id = id;
     this.x = x;
     this.x = x;
     this.show = show;
     this.show = _show;
   }
   }


   function show() {  
   function _show() {  
     document.getElementById(this.id).style.visibility = "visible";
     document.getElementById(this.id).style.visibility = "visible";
   }
   }
Line 227: Line 294:
   };
   };
})();
})();
</source>
== Step 3: The Factory Factory ==
Hide the constructor on a function and pass it to makeFactory.
=== Solution I: A Decorator Factory Factory That Takes a Constructor ===
Decorator Factory Factory takes a constructor that takes an element id as its first argument and any number of extra arguments.
<source lang="javascript">
// Create a factory.
var ElementWrapperFactory = makeFactory(function() {
// The constructor.
    function ElementWrapper (id, x, y) {
      this.id = id;
      this.x = x;
      this.y = y;
      this.show = _show;
    }
    function _show() {
      document.getElementById(this.id).style.visibility = "visible";
    }
    return ElementWrapper;
  }()
);
// The Factory Factory.
function makeFactory(ctor) {
  var instances = {};
  return {
    getById : function(id) {
      return instances[id] || (instances[id] = newApply(ctor, arguments));
    }
  };
}
// Implementation, or usage.
var ew = ElementWrapperFactory.getById("globalWrapper", 1, 2);
alert(ew.x); // 1.
</source>
==== Add newApply to Scope of makeFactory ====
Function rewriting.
<source lang="javascript">
function makeFactory(ctor) {
  return (makeFactory = function(ctor) {
    var instances = {};
 
    return {
      getById : function(id) {
        return instances[id] || (instances[id] = newApply(ctor, arguments));
      }
    };
  })(ctor);
 
  function F(){};
  function newApply(fun, args) {
      var 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>
=== Introduce Parameter Object ===
[[JavaScript/Notes/ParameterObject|Parameter Object]]s are useful replacements for parameter lists, particularly long ones, or sets of parameters that always go together.
In this case, Parameter Objects are useful because they can have any number of properties needed by any constructor.
==== Be Smart, not Clever ====
By using a <code>config</code> parameter object, we can define a parameter list of two, thereby avoiding the need for the clever <code>newApply</code> trick.
=== Solution II: Replace ArgumentList with Parameter Object ===
<source lang="javascript">
// Create a factory.
var ElementWrapperFactory = makeFactory(function() {
    function ElementWrapper (id, config) {
      this.id = id;
      this.x = config).x;
      this.show = _show;
    }
    function _show() {
      document.getElementById(this.id).style.visibility = "visible";
    }
    return ElementWrapper;
  }()
);
// Config-based Factory Factory.
function makeFactory(ctor) {
  var instances = {};
  return {
    getById : function(id, config)) {
      if(instances.hasOwnProperty(id)) return instances[id];
      return instances[id] = new ctor(id, config));
    }
  };
}
// Implementation, or usage.
var ew = ElementWrapperFactory.getById("globalWrapper", { x : 1 });
alert(ew.x); // 1.
</source>
=== Solution IIb. ===
==== The Factory Constructor ====
<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>
Tip: Make a defensive copy of config properties. That way, later changes to the config won't affect your object.
==== Usage ====
<source lang="javascript">
ElementWrapper.getById("globalWrapper").title
</source>
</source>


=== Source Code ===
=== Source Code ===
<p>
<p>
[https://github.com/GarrettS/ape-javascript-library/blob/master/src/APE.js#L130
Added to APE core, in variation, many years ago, and well-used. There, the instances property is publicly accessible so that a finalizer or "clean up" function can be run, for example, to remove event handlers.
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 https://github.com/GarrettS/ape-javascript-library/blob/master/src/APE.js#L130]
</p>
</p>

Latest revision as of 22:34, 5 April 2014

Factory Aspect, Added to a Decorator

[edit | edit source]

This 2-part class will be a soup-to-nuts explanation of widget design, a description the Factory pattern, a recapitulation of Event Bubbling strategy, and generic concepts for creating and mixing design patterns.

Prerequisite

[edit | edit source]

You'll need to have a good understanding of Functions and context, and Scope. Suggested review includes Singleton and the event bubbling section of ClassName Swap lesson.

Object Oriented Design

[edit | edit source]

The basic principle for all design patterns is: Encapsulate the parts that vary.

Animation Transition

Pseudocode:

color:      rgb(12, 44, 89)  -->  rgb(233, 197, 17);
marginLeft: 0                -->  -9px;
padding:    0                -->  12px;
zIndex:     1                -->  100

Each animation's time duration is passed to it when it is called. The style values are supplied by the caller.

The part that varies is the Style Value Type, and that depends on the Style Property.

StyleTransition.js

Decorator

[edit | edit source]

Factory is useful when you want to create at most one of an object for any associated element, off a bubbled event, using an object pool to "get or create" a wrapper, or Decorator.

Step 1: getById

[edit | edit source]

<source lang=javascript> function ElementWrapper(id, x) {

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

}

ElementWrapper.instances = {};

ElementWrapper.getById = function(id, x) {

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

}; </source>

Problems

[edit | edit source]

Problem: Not DRY. Cannot be arbitrarily reused other constructor functions. Problem: ArgumentList (§11.2) is set to two.

Problem: Encapsulation. The factory and the constructor are exposed publicly.

Decorator Factory Aspect

[edit | edit source]

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
Add functionality to an object by wrapping it. (Wikipedia link)
Factory Pattern
The Factory pattern is a creational design pattern that encapsulates the processes of creating objects (Wikipedia link). That way, we can create objects lazily and pool them in an object pool.
Aspect
introduces separation of concern(s), as modularization of functionality (Wikipedia link)

Decorator Examples

[edit | edit source]

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

Factory Example

[edit | edit source]

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

[edit | edit source]

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

[edit | edit source]

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

[edit | edit source]

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

[edit | edit source]

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

[edit | edit source]

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]]. (see Function.prototype.apply, [[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 F(){}; function newApply(fun, args) {

   var 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>

Usage

Early versions of this method appeared in APE JavaScript library, which has been copied.

What's newApply Good For?

[edit | edit source]

Generic getById function I wanted, to use aspect with any constructor function.

Library Aspect
[edit | edit source]

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

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

} </source>

Implementation
[edit | edit source]

<source lang="javascript"> function ElementWrapper(id, x, y) {

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

} ElementWrapper.getById = getById; </source>

Usage
[edit | edit source]

<source lang="javascript"> ElementWrapper.getById("a", 2, 3).y; // 3 </source> Example in jsbin: live example.

Using the Generic getById

[edit | edit source]

This getById method 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. All other arguments will be passed to the constructor using newApply.

<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

[edit | edit source]

I have used a modified version of this approach for many widgets, including

Autocomplete (can't run off Github pages because there is no server side processing). This pattern is useful for building widgets that can be initialized lazily, on a bubbled event.

(slider uses overflow hidden).

Reusable Concept

[edit | edit source]

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


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

[edit | edit source]

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

Step 2: Factory

[edit | edit source]

Problem: Publicly Exposed Constructor

[edit | edit source]

Solution: Hide the constructor in a closure.

[edit | edit source]

<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>

Step 3: The Factory Factory

[edit | edit source]

Hide the constructor on a function and pass it to makeFactory.

Solution I: A Decorator Factory Factory That Takes a Constructor

[edit | edit source]

Decorator Factory Factory takes a constructor that takes an element id as its first argument and any number of extra arguments.

<source lang="javascript"> // Create a factory. var ElementWrapperFactory = makeFactory(function() {

// The constructor.

   function ElementWrapper (id, x, y) {
     this.id = id;
     this.x = x;
     this.y = y;
     this.show = _show;
   }
   function _show() { 
     document.getElementById(this.id).style.visibility = "visible";
   }
   return ElementWrapper;
 }()

);


// The Factory Factory. function makeFactory(ctor) {

 var instances = {};
 return { 
   getById : function(id) {
     return instances[id] || (instances[id] = newApply(ctor, arguments));
   }
 };

}

// Implementation, or usage. var ew = ElementWrapperFactory.getById("globalWrapper", 1, 2); alert(ew.x); // 1. </source>

Add newApply to Scope of makeFactory

[edit | edit source]

Function rewriting.

<source lang="javascript"> function makeFactory(ctor) {

 return (makeFactory = function(ctor) { 
   var instances = {};
 
   return { 
     getById : function(id) {
       return instances[id] || (instances[id] = newApply(ctor, arguments));
     }
   };
 })(ctor);
 
 function F(){};
 function newApply(fun, args) {
     var 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>

Introduce Parameter Object

[edit | edit source]

Parameter Objects are useful replacements for parameter lists, particularly long ones, or sets of parameters that always go together.

In this case, Parameter Objects are useful because they can have any number of properties needed by any constructor.

Be Smart, not Clever

[edit | edit source]

By using a config parameter object, we can define a parameter list of two, thereby avoiding the need for the clever newApply trick.

Solution II: Replace ArgumentList with Parameter Object

[edit | edit source]

<source lang="javascript"> // Create a factory. var ElementWrapperFactory = makeFactory(function() {

   function ElementWrapper (id, config) {
     this.id = id;
     this.x = config).x;
     this.show = _show;
   }
   function _show() { 
     document.getElementById(this.id).style.visibility = "visible";
   }
   return ElementWrapper;
 }()

);


// Config-based Factory Factory. function makeFactory(ctor) {

 var instances = {};
 return { 
   getById : function(id, config)) {
     if(instances.hasOwnProperty(id)) return instances[id];
     return instances[id] = new ctor(id, config));
   }
 };

}


// Implementation, or usage. var ew = ElementWrapperFactory.getById("globalWrapper", { x : 1 }); alert(ew.x); // 1. </source>

Solution IIb.

[edit | edit source]

The Factory Constructor

[edit | edit source]

<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

[edit | edit source]

<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>

Tip: Make a defensive copy of config properties. That way, later changes to the config won't affect your object.

Usage

[edit | edit source]

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

Source Code

[edit | edit source]

Added to APE core, in variation, many years ago, and well-used. There, the instances property is publicly accessible so that a finalizer or "clean up" function can be run, for example, to remove event handlers. https://github.com/GarrettS/ape-javascript-library/blob/master/src/APE.js#L130