In case you're locked into the inheritance paradigm, this section demonstrates multiple approaches to achieving similar results for a simple problem: modeling a circle object. In addition to being a good exercise in demonstrating some out-of-the-box thinking, this section also applies some of the language tools you learned about in Chapter 2.
JavaScript programmers have been simulating classes with
Function objects for quite a while—sometimes at the expense of
abusing the language, other times effectively to solve a particular
problem. The inherent nature of a JavaScript Function object is the
very mechanism that provides the footing for simulating classes.
Namely, it acts as a constructor function that
is used in conjunction with the new
operator to create object instances,
and it provides the template for those object instances that are
created.
To illustrate, Example 10-1
provides a short code snippet that approximates a simple Shape
class in JavaScript. Note that by
convention, classes usually begin with a capital letter.
Tip
For just an added touch of simplicity, the examples in this chapter do not use namespaces for qualifying objects. In general, however, you will want to use namespaces, and we'll pick back up using namespaces in Chapter 12.
Example 10-1. A typical JavaScript class
// Define a class function Shape(centerX, centerY, color) { this.centerX = centerX; this.centerY = centerY; this.color = color; }; // Create an instance s = new Shape(10, 20, "blue");
Once the JavaScript interpreter executes the function
definition, a Shape
object exists
in memory and acts as the prototypal object for creating object
instances whenever its constructor function is invoked with the
new
operator.
For completeness, note that you could have defined the
Shape
object using Base's extend
function in a slightly more compact fashion:
// Create a Function object function Shape( ) {} // Extend its prototype with some reasonable defaults dojo.extend(Shape, { centerX : 0, centerY : 0, color : "" });
Unfortunately, you could only have fun with this class for
about three seconds because you'd start to get really bored and want
to model something a little more concrete—like a specific kind of
shape. While you could approximate a new class
such as a circle entirely from scratch, a more maintainable approach
would be to have a circle class inherit from the shape class that's
already defined because all circles are shapes. Besides, you already
have a perfectly good Shape
class
lying around, so why not use it?
Example 10-2 demonstrates one approach to accomplishing this inheritance relationship in JavaScript.
Example 10-2. Typical JavaScript inheritance
// Define a subclass function Circle(centerX, centerY, color, radius) { // Ensure the subclass properties are added to the superclass by first //assigning the subclass a reference to the constructor function and then //invoking the constructor function inside of the superclass. this.base = Shape; this.base(centerX, centerY, color); // Assign the remaining custom property to the subclass this.radius = radius; }; // Explicitly chain the subclass's prototype to a superclass so that any new properties //that are dynamically added to the superclass are reflected in subclasses Circle.prototype = new Shape; // Create an instance c = new Circle(10, 20, "blue", 2); //The circle IS-A shape
While you may have found that to be an interesting exercise, it probably wasn't as short and sweet as you might have first thought, and it probably wasn't terribly central to that really cool web application you've been trying to finish up.
For the sake of demonstrating an alternate paradigm to the typical inheritance groupthink, consider Example 10-3's approach of using mixins to model shapes and circles in a different way. It's especially noteworthy to make the connection that mixins heavily leverage duck typing and has-a relationships. Recall that the concept of ducktyping is based upon the idea that if something quacks like a duck and acts like a duck, then it may as well be a duck. In our circumstance, the concept translates to the idea that if an object has the properties you'd expect of a shape or circle, that's good enough to call it as much. In other words, it doesn't matter what the object really is as long as it has the right properties.
Example 10-3. Mixing in as an alternative to inheritance
//Create a plain old Object to model a shape var shape = {} //Mix in whatever you need to make it "look like a shape and quack like a shape" dojo.mixin(shape, { centerX : 10, centerY : 20, color : "blue" }); //later on you need something else. No problem, mix it right in dojo.mixin(shape, { radius : 2 }); //Now the shape HAS-A radius
For the record, this mixin example is not intended to be an exact drop-in replacement for the previous example that used prototypal inheritance; rather, this mixin example is intended to demonstrate that there are various ways of approaching a problem.
As yet another approach to modeling a relationship between a shape and a circle, consider the pattern of delegation, shown in Example 10-4. Whereas the mixin pattern actually copies properties into a single object instance, the delegation pattern passes on responsibility for some set of properties to another object that already has them.
Example 10-4. Delegation as an alternative to inheritance
//Create a plain old Object var shape = {} //Mix in what you need for this instance dojo.mixin(shape, { centerX : 10, centerY : 20, color : "blue" }); //delegate circle's responsibility for centerX, centerY, and color to shape //mix in the radius directly circle = dojo.delegate(shape, { radius : 2 });
The key takeaways from this revision are that the radius
property defined in the object
literal is mixed into the circle, but the remaining shape properties
are not. Instead, the circle delegates to the shape whenever it is
asked for a property that it does not have itself. To sum it all
up:
Requests for
radius
are provided directly bycircle
becauseradius
got mixed in.Requests for
centerX
,centerY
, andcolor
are delegated to theshape
because they don't exist on thecircle
itself (loosely speaking).A request for any other property returns
undefined
by definition because it doesn't exist in either thecircle
or theshape
.
Although the working example is so simple that the mixin pattern makes more sense to use, the delegation pattern certainly has plenty of uses, especially in situations in which you have large number of objects that all share a particular subset of things that are in common.
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.