Free email newsletter: “ES.next News

2012-02-06

Exemplar patterns in JavaScript

This post explores exemplars (factories for objects) and how to implement them. The term exemplar has been proposed by Allen Wirfs-Brock to avoid the term class, which is not a good fit for JavaScript: Exemplars are similar to classes, but they are not classes.

Exemplars

An exemplar is a construct that creates objects. Each of the following kinds of exemplars produces instances of a single fixed type:
  • Regular expression literals: produce instances of RegExp:
        > /abc/ instanceof RegExp
        true
    
  • Array initializers: produce instances of Array.
        > [1, 2, 3] instanceof Array
        true
    
  • Object initializers: look as follows.
        var jane = {
            name: "Jane",
            describe: function () {
                return "Person called "+this.name;
            }
        }
    
    Each object produced by an object initializer (without non-standard extensions) is a direct instance of Object:
        > jane instanceof Object
        true
    
If you need more flexibility you can turn to function exemplars (Sect. 2) and object exemplars (Sect. 4) where you can specify the type of the instances per exemplar.

Function exemplars (constructors)

A function can be used as an exemplar if invoked via the new operator. It becomes a so-called constructor. We have previously seen how to create the single “person” jane via an object initializer. The following constructor is an exemplar for this kind of object.
    function Person(name) {
        this.name = name;
    }
    Person.prototype.describe = function () {
        return "Person called "+this.name;
    };
This is how you use the new operator to create an object:
    var jane = new Person("Jane");
jane is considered in instance of Person. You can check that relationship via the instanceof operator:
    > jane instanceof Person
    true
There are two parts for setting up an instance of Person: The instance-specific properties are added via the constructor:
    > jane.name
    'Jane'
Properties that are shared by all instances (mostly methods) are inherited from Person.prototype:
    > jane.describe()
    'Person called Jane'
    > jane.describe === Person.prototype.describe
    true
You can verify that the single object Person.prototype is indeed the prototype of all instances of Person:
    > Person.prototype.isPrototypeOf(jane)
    true
The prototype-of relationship is also used to check whether an object is an instance of a constructor. The expression
    jane instanceof Person
is actually implemented as
    Person.prototype.isPrototypeOf(jane)

Optional parameters

Often, one would like the same constructor to set up its instances in different ways. The following constructor for two-dimensional points can be invoked with either zero arguments or two arguments. In the former case, it produces a point (0,0), in the latter case, one specifies x coordinate and y coordinate.
    function Point(x, y) {
        if (arguments.length >= 2) {
            this.x = x;
            this.y = y;
        } else {
            this.x = 0;
            this.y = 0;
        }
    }
This idea can be extended to more than two possible argument combinations. For example, the following constructor for points with a color can receive 3 arguments, 2 arguments, or 0 arguments.
    function ColorPoint(x, y, color) {
        if (arguments.length >= 3) {
            this.color = color;
        } else {
            this.color = black;
        }
        if (arguments.length >= 2) {
            this.x = x;
            this.y = y;
        } else {
            this.x = 0;
            this.y = 0;
        }
    }
There is a possibility to write this code more compactly: Missing parameters are undefined in JavaScript which means that we want to make assignments like
    this.x = (x !== undefined ? x : 0);
    // this.x = (... ? value : default)
The following function implements this kind of check:
    function valueOrDefault(value, theDefault) {
        return (value !== undefined && value !== null
            ? value : theDefault);
    }
Now ColorPoint is more compact:
    function ColorPoint(x, y, color) {
        this.x     = valueOrDefault(x, 0);
        this.y     = valueOrDefault(y, 0);
        this.color = valueOrDefault(color, "black");
    }
You can also use the || operator:
    function ColorPoint(x, y, color) {
        this.x     = x || 0;
        this.y     = y || 0;
        this.color = color || "black";
    }
Explanation:
    left || right
evaluates to left if Boolean(left) is true and to right, otherwise. Caveat: With ||, the following values will all be interpreted as the argument being missing:
  • undefined, null
  • false
  • +0, -0, NaN
  • ""
You therefore cannot assign the empty string to color, because
    > "" || "black"
    'black'

Option objects

Positional optional parameters are limiting with regard to what parameters can be omitted: If a parameter that you want to omit isn’t at the end, you have to insert a placeholder value such as undefined or null. Furthermore, if there are many parameters, you quickly lose sight of the meaning of each one. The idea of an option object is to provide optional parameters as an object that is created via an object initializer. For ColorPoint that would look as follows.
    > new ColorPoint({ color: "black" })
    { color: 'black', x: 0, y: 0 }

    > new ColorPoint({ x: 33 })
    { x: 33, y: 0, color: 'black' }

    > new ColorPoint()
    { x: 0, y: 0, color: 'black' }
With a helper function, it is remarkably easy to transfer the optional values to the instance under construction and to fill in defaults for missing properties:
    function ColorPoint(options) {
        _.defaults(this, options, {
            x: 0,
            y: 0,
            color: "black"
        });
    }
Above we have used the function defaults of the Underscore library [1]. It copies all of the properties of options to this that don’t exist there, yet. And similarly fills in the defaults via the third argument. [2] has more information on option objects.

Chainable setters

One can achieve a style that is very similar to an option object by creating a setter for each optional parameter. That looks as follows:
    > new ColorPoint().setX(12).setY(7)
    { x: 12, y: 7, color: 'black' }

    > new ColorPoint().setColor("red")
    { x: 0, y: 0, color: 'red' }
Naturally, each of the setters must return this, so that setters can be chained and that the final result of such a chain is always the instance of ColorPoint.
    function ColorPoint() {
        // Set default values
        this.x     = 0;
        this.y     = 0;
        this.color = "black";
    }
    ColorPoint.prototype.setX = function (x) {
        this.x = x;
        return this;
    }
    ColorPoint.prototype.setY = function (y) {
        this.y = y;
        return this;
    }
    ColorPoint.prototype.setColor = function (color) {
        this.color = color;
        return this;
    }
Given how mechanical writing such setters is, we can write a method withSetters that does it for us:
    var ColorPoint = function (x, y, color) {
        this.x     = 0;
        this.y     = 0;
        this.color = "black";
    }.withSetters(
        "x", "y", "color"
    );
withSetters is a method that, if applied to a function, adds setters to that function’s prototype:
    Function.prototype.withSetters = function (/*setter names*/) {
        var Constr = this;
        Array.prototype.forEach.call(arguments, function (propName) {
            var capitalized = propName[0].toUpperCase() + propName.slice(1);
            var setterName = "set" + capitalized;
            Constr.prototype[setterName] = function (value) {
                this[propName] = value;
                return this;
            };
        });
        return this;
    };
Refining this approach, we can use an object initializer to specify both setter names and default values at the same time:
    var ColorPoint = function (x, y, color) {
        this.applyDefaults();
    }.withSetters({
        x: 0,
        y: 0,
        color: "black"
    });
The defaults are added to the fresh instance of ColorPoint via the method applyDefaults. Apart from that method, not much changes – now the setter names are extracted from an object.
    Function.prototype.withSetters = function (props) {
        var Constr = this;
        Constr.prototype.applyDefaults = function () {
            _.defaults(this, props);
        }
        Object.keys(props).forEach(function (propName) {
            var capitalized = propName[0].toUpperCase() + propName.slice(1);
            var setterName = "set" + capitalized;
            Constr.prototype[setterName] = function (value) {
                this[propName] = value;
                return this;
            };
        });
        return this;
    };

Initialization methods

Sometimes there are several mutually exclusive “modes” or ways of initializing an object. Using the same constructor for all modes is tricky. It would be better to have a clear indication of which mode is active. The problem of using the same name for different operations is nicely illustrated by a well-known quirk of JavaScript’s Array constructor. The first operation is to create an empty array with the given length:
    > new Array(3)
    [ , ,  ]
The second operation is to create an array with given elements:
    > new Array("a", "b", "c")
    [ 'a', 'b', 'c' ]
However, the second operation is problematic, because if you want to create an array that has a single natural number in it, operation 1 takes over and doesn’t let you do it. A better solution is to again use chainable setters and give each operation a different name. As an example, let’s look at a constructor Angle whose instances can be initialized in either degrees or radians.
    > new Angle().initDegrees(180).toString()
    '3.141592653589793rad'
Note how we have separated instantiation (instance creation) via new Angle() from initialization via initDegrees(). Constructors usually perform both tasks; here we have delegated the latter task to a method. Angle can be implemented as follows.
    function Angle() {
    }
    Angle.prototype.initRadians = function (rad) {
        this.rad = rad;
        return this;
    };
    Angle.prototype.initDegrees = function (deg) {
        this.rad = deg * Math.PI / 180;
        return this;
    };
    Angle.prototype.toString = function () {
        return this.rad+"rad";
    };
initRadians and initDegrees are not really setters, they are initialization methods. Initialization methods differ from setters in two ways: First, they can have more than one parameter or no parameter. In contrast, setters usually have exactly one parameter. Second, you often want to ensure that if a method is invoked on an instance, that instance has been initialized beforehand. And, possibly, one shouldn’t initialize more than once. The following code uses the boolean flag _initialized to perform these two checks.
    function Angle() {
        this._initialized = false;
    }

    Angle.prototype._forbidInitialized = function () {
        if (this._initialized) {
            throw new Error("Already initialized");
        }
        this._initialized = true;
    };
    Angle.prototype.initRadians = function (rad) {
        this._forbidInitialized();
        this.rad = rad;
        return this;
    };
    Angle.prototype.initDegrees = function (deg) {
        this._forbidInitialized();
        this.rad = deg * Math.PI / 180;
        return this;
    };

    Angle.prototype._forceInitialized = function () {
        if (!this._initialized) {
            throw new Error("Not initialized");
        }
    };
    Angle.prototype.toString = function () {
        this._forceInitialized();
        return this.rad+"rad";
    };

Static factory methods

Static factory methods are an alternative to initialization methods. They are methods of the constructor that create instances. Given that a constructor is similar to a class in class-based languages, such methods are often called static. They are called factory methods, because they produce instances. Creating an angle with a static factory method instead of a constructor and an initialization method looks as follows.
    > var a = Angle.withDegrees(180);
    > a.toString()
    '3.141592653589793rad'
    > a instanceof Angle
    true
This is an implementation:
    function Angle(rad) {
        this.rad = rad;
    }
    Angle.withRadians = function (rad) {
        return new Angle(rad);
    };
    Angle.withDegrees = function (deg) {
        return new Angle(deg * Math.PI / 180);
    };
    Angle.prototype.toString = function () {
        return this.rad+"rad";
    };
The point of factory methods is to replace the constructor. We therefore want to make it impossible to accidentally invoke new Angle(), or even just Angle as a function. Simply hiding Angle won’t do, because we need it for instanceof.

Guarding the constructor, approach 1. The constructor throws an error if it isn’t called with a value that is only known to the factory methods. We keep the value secret by putting it inside an immediately-invoked function expression (IIFE, [3]).

    var Angle = function () {
        var constrGuard = {};
        function Angle(guard, rad) {
            if (guard !== constrGuard) {
                throw new Error("Must use a factory method");
            }
            this.rad = rad;
        }
        Angle.withRadians = function (rad) {
            return new Angle(constrGuard, rad);
        };
        Angle.withDegrees = function (deg) {
            return new Angle(constrGuard, deg * Math.PI / 180);
        };
        Angle.prototype.toString = function () {
            return this.rad+"rad";
        };
        return Angle;
    }();
Guarding the constructor, approach 2. An alternative is to not use the constructor to create an instance of Angle, but
    Object.create(Angle.prototype)
The factory methods now use the function createInstance that implements this approach.
    var Angle = function () {
        function Angle() {
            throw new Error("Must use a factory method");
        }
        function createInstance(rad) {
            var inst = Object.create(Angle.prototype);
            inst.rad = rad;
            return inst;
        }
        Angle.withRadians = function (rad) {
            return createInstance(rad);
        };
        Angle.withDegrees = function (deg) {
            return createInstance(deg * Math.PI / 180);
        };
        Angle.prototype.toString = function () {
            return this.rad+"rad";
        };
        return Angle;
    }();
Interaction:
    > var a = Angle.withDegrees(180);
    > a.toString()
    '3.141592653589793rad'
    > a instanceof Angle
    true

Creating instances of multiple types in a single location

Sometimes, there is a hierarchy of types and you want to create instances of those types in a single location. For example, expressions:
    Expression
    ├── Addition
    └── Integer
The type Expression has two subtypes: Integer and Addition (whose operands can be expressions). Assuming that Expression is abstract and will never be instantiated, parsing a text string will produce an instance of either Addition or Integer. The common way to implement such an operation is via a factory method:
    Expression.parse = function (str) {
        if (/^[-+]?[0-9]+$/.test(str)) {
            return new Integer(str);
        }
        ...
    }
However, JavaScript also lets you return an arbitrary object from a constructor. Hence, while the following assertion usually holds:
    new C() instanceof C
it doesn’t have to:
    > function C() { return {} }
    > new C() instanceof C
    false
That allows you to implement expression parsing as follows:
    function Expression(str) {
        if (/^[-+]?[0-9]+$/.test(str)) {
            return new Integer(str);
        }
        ...
    }
Then the following assertion holds:
    new Expression("-125") instanceof Integer
Most JavaScript engines perform an automatic optimiziation and don’t create an instance of the constructor, if this isn’t accessed and another object is (explicitly) returned.

The new operator

Invocation

    new <function-valued expression>(arg1, arg2, ...)
    var inst = new Constr();
    var inst = new Constr;
The function-valued expression above is usually either
  1. any expression, in parentheses, that evaluates to a function
  2. an identifier, optionally followed by one or more property accesses
#1 matters when you want to apply new to the result of a function, e.g. the following function foo().
    function foo() {
        function bar(arg) {
            this.arg = arg;
        }
        return bar;
    }
Calling the result of foo() as a function is easy, you simply append parentheses with arguments:
    foo()("hello")
To use the result as the operand of new, you have to add additional parentheses:
    > new (foo())("hello")
    { arg: 'hello' }
Without parentheses around foo(), you would invoke foo as a constructor and then call the result as a function, with the argument "hello". That the operand of new is finished with the first parentheses is an advantage when it comes to invoking a method on a newly created object. As an example, consider a constructor for colors.
    function Color(name) {
        this.name = name;
    }
    Color.prototype.toString = function () {
        return "Color("+this.name+")";
    }
You can create a color and then immediately invoke a method on it:
    > new Color("green").toString()
    'Color(green)'
The above expression is equivalent to
    (new Color("green")).toString()
However, property accesses before the parentheses are considered part of the operand of new. That is handy when you want to put a constructor inside an object:
    var namespace = {};
    namespace.Color = function (name) {
        this.name = name;
    };
Interaction:
    > new namespace.Color("red")
    { name: 'red' }

The new operator ignores a bound value for `this`

Functions have a method bind() that lets you create a new function with a fixed value for this, whose first 0 to n parameters are already filled in. Given the definitions.
    var obj = {};
    function F() {
        console.log("this === obj? " + (this === obj));
    }
Then you can create a new function via bind:
    var boundF = F.bind(obj);
boundF works as expected when called as a function:
    > boundF()
    this === obj? true
    undefined
The result of boundF() is undefined. However, new overrides the bound this with a new instance:
    > new boundF() instanceof F
    this === obj? false
    true
    > function Constr() { console.log(this === obj) }
    undefined
    > (Constr.bind(obj))()
    true
    undefined
    > new (Constr.bind(obj))()
    false
    {}

The new operator doesn’t work with apply()

You can call the Date constructor as follows.
    new Date(2011, 11, 24)
If you want to provide the arguments via an array, you cannot use apply:
    > new Date.apply(null, [2011, 11, 24])
    TypeError: function apply() { [native code] } is not a constructor
The reason is obvious: new expects Date.apply to be a constructor. More work is required to achieve for constructors what apply achieves for functions [4].

Object exemplars

One normally uses function exemplars to create objects:
    function Person(name) {
        this.name = name;
    }
    Person.prototype.describe = function () {
        return "Person called "+this.name;
    };
An instance is created via the new operator:
    > new Person("Jane") instanceof Person
    true
ECMAScript 5 introduced a new way of creating instances:
    > Object.create(Person.prototype) instanceof Person
    true
The above invocation of Object.create() produced an object whose prototype is Person.prototype. There are two things that can be improved: First, we can get rid of the function exemplar Person and work directly with the the prototype object. Second, we would like to initialize a newly created instance in the same manner that constructors do.

Working directly with the prototype object. The prototype object is now our exemplar, we want it to bear the name Person, so that we can discard the constructor. This looks as follows.

    var Person = {
        describe: function () {
            return "Person called "+this.name;
        }
    };
We can’t use instanceof, any more, because it is an operator designed to work with function exemplars. However, isPrototypeOf() works nicely:
    > Person.isPrototypeOf(Object.create(Person))
    true
Initialization. We still haven’t initialized our instances of Person. To do so, we introduce a method called init().
    var Person = {
        init: function (name) {
            this.name = name;
            return this;
        },
        describe: function () {
            return "Person called "+this.name;
        }
    };
Note how similar init() is to a constructor. We use this object exemplar as follows:
    > var jane = Object.create(Person).init("Jane");

    > Person.isPrototypeOf(jane)
    true

    > jane.describe()
    'Person called Jane'
You can consult [5] for more information on object exemplars, including a library for working with them.

Topics not covered by this post

Two topics that are relevant to object creation were not covered by this post:

References

  1. Trying out Underscore on Node.js
  2. Keyword parameters in JavaScript and ECMAScript.next
  3. JavaScript variable scoping and its pitfalls
  4. Spreading arrays into arguments in JavaScript
  5. Prototypes as classes – an introduction to JavaScript inheritance
  6. JavaScript inheritance by example
  7. Subtyping JavaScript built-ins

No comments: