JavaScript/Notes/Factory: Difference between revisions
Line 181: | Line 181: | ||
</source> | </source> | ||
<h4>Usage</h4> | |||
<source> | |||
function Blah(id) { | |||
this.id = id; | |||
} | |||
Blah.prototype.toString = function() { | |||
return"[Blah] id = " + this.id; | |||
}; | |||
document.body.textContent = newApply(Blah, ["test"]); | |||
</source> | |||
Early versions of this method appeared in APE JavaScript library, which has been copied. | Early versions of this method appeared in APE JavaScript library, which has been copied. | ||
Revision as of 09:27, 29 November 2013
Factory Aspect, Added to a Decorator
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
You'll need to review the lessons for Singleton and the event bubbling section of ClassName Swap lesson. I'll recap on those but not for long.
Object Oriented Design
The basic principle for all design patterns is: Encapsulate the parts that vary.
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 parts that vary are: Style Property, and Style Value Type.
Decorator
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
<source lang=javascript> function ElementWrapper(id, x) {
this.id = id; this.x = x;
}
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: ArgumentList
(§11.2) is set to two.
Problem: Encapsulation. The factory and the constructor are exposed publicly.
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
- 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
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]]
. (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
<source> function Blah(id) {
this.id = id;
}
Blah.prototype.toString = function() {
return"[Blah] id = " + this.id;
};
document.body.textContent = newApply(Blah, ["test"]); </source> Early versions of this method appeared in APE JavaScript library, which has been copied.
What's newApply Good For?
Generic getById
function I wanted, to use aspect with any constructor function.
Library Aspect
<source lang="javascript"> function getById(id) {
if(!this.hasOwnProperty("instances")) this.instances = {}; 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>
Using the Generic getById
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
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.
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.
Links
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.
Introduce Parameter Object
When dealing with long parameter lists, or sets of parameters that always go together, use a Parameter Object instead.
Be Smart, not Clever
By using a config
parameter object, we can define a parameter list of two, thereby avoiding 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]