The previous section introduced how Dojo simulates class-based
inheritance and pointed out some critical issues involving
JavaScript's finer points that are central to developing with Dojo.
Although the primary example demonstrated single inheritance in which
a Shape
superclass provided the
basis for a Circle
subclass, Dojo
also provides a limited form of multiple inheritance.
The process of defining inheritance relationships by weaving
together Function objects in this way is referred to as
prototype chaining because it's through
JavaScript's Object.prototype
property that the hierarchy is defined. (Recall that Example 10-2 illustrated this
concept within the boilerplate of manually defining an inheritance
relationship between a shape and a circle.)
Dojo simulates class-based inheritance by building upon the
concept of prototype chaining to establish the hierarchy for single
inheritance contexts. However, employing multiple-inheritance
relationships is a little bit different because JavaScript limits
Function objects to having only one built-in prototype
property.
As you might imagine, there are a number of approaches that could be used to circumvent this issue. The approach that Dojo uses is to leverage prototype chaining so that you define a single prototypical ancestor that is the basis for prototype chaining—but at the same time, allowing you to provide other mixins that get injected into the prototypal ancestor. In other words, a class can have only one prototype, but the Function objects that the class creates can get "stamped" with as many constructor functions as you want to throw at it. Granted, the prototypes of those constructor functions won't be taken into account later in the life of the object, but they can be leveraged in very powerful ways nonetheless. Think of these mixins as "interfaces that actually do stuff" or "interface + implementation."
Tip
In multiple-inheritance relationships, the ancestors are
provided to dojo.declare
inside
of a list. The first element of the list is known as the
prototypical ancestor, while the latter is commonly a mixin
ancestor, or more concisely, a "mixin."
Here's what multiple inheritance looks like with Dojo. The only
thing that's different is that the third parameter to dojo.declare
is a list instead of a Function
object. The first element in that list is the prototypical ancestor,
while the other is a mixin:
<html> <head> <title>Fun with Multiple Inheritance!</title> <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js"> </script> <script type="text/javascript"> dojo.addOnLoad(function( ) { //A perfectly good Dojo class with a reasonable constructor and no //direct ancestors. dojo.declare("Tiger", null, { _name: null, _species: null, constructor : function(name) { this._name = name; this._species = "tiger"; console.log("Created ",this._name +,"the ",this._species); } }); //Another perfectly good Dojo class with a reasonable constructor //and no direct ancestors. dojo.declare("Lion", null, { _name: null, _species: null, constructor: function(name) { this._name = name; this._species = "lion"; console.log("Created ",this._name +," the ",this._species); } }); //A Dojo class with more than one ancestor. The first ancestor is the //prototypical ancestor, while the second (and any subsequent //functions) are mixins. Take special note that each of the //superclass constructors execute before this subclass's constructor //executes -- and there's really no way to get around that. dojo.declare("Liger", [Tiger, Lion], { _name: null, _species: null, constructor : function(name) { this._name = name; this._species = "liger"; console.log("Created ",this._name, " the ", this._species); } }); lucy = new Liger("Lucy"); console.log(lucy); }); </script> </head> <body> </body> </html>
If you open the previous example and look at the console output
in Firebug shown in Figure 10-3, you'll see that both
a Tiger
and Lion
are created before a Liger
is created. Just like the previous
example with shapes, you do get your subclass, but not until after the
necessary superclasses have been created, complete with constructor
methods running and all.
Figure 10-3. Although you do eventually get your Liger, it's not until after the necessary superclasses have been created and properly initialized
In the earlier example involving shapes, there was no
particular need to be concerned with the argument list from a
Circle
getting passed up to a
Shape
because a Circle
built directly upon a Shape
. Furthermore, it made good sense and
was even convenient to include Shape
's constructor
argument as the first argument
of Circle
's constructor
. In this past example with
lions, tigers, and ligers, the constructor
s are all single argument
functions that do the same thing, so there's no real issue there,
either.
But wait—what if Tiger
and
Lion
each had custom constructor
s? For example, Tiger
's constructor
might specify arguments
corresponding to the name and number of stripes, while Lion
's constructor
might specify the name and
mane length. How would you define a Liger
's constructor
to handle a situation like
that? The very same arguments that are passed into Liger
's constructor
will be passed into Tiger
's constructor as well as Lion
's constructor
, and that just doesn't make
any sense.
In this particular instance—when two or more superclasses each
require their own custom parameters—your best bet, if you have the
option, is to pass in an associative array of named parameters and
use these in your constructor
instead of relying on the arguments
list directly. Passing in custom
parameters to superclasses in a multiple-inheritance relationship is
not well-supported as of Dojo 1.1, although discussion for this kind
of impedance matching is under consideration for a future
release.
In general, a convenient pattern is to design multiple-inheritance relationships such that superclasses don't have constructors that require any arguments. The advantage of this approach is that purposefully defining superclasses without arguments allows the subclass to receive and process as many custom arguments as it needs, while ensuring that any superclasses up the inheritance chain won't be affected by them. After all, because they don't use them in any way, they can't be affected.
Get Dojo: The Definitive Guide now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.