One Problem, Many Solutions

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.

Typical JavaScript Inheritance

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.

Mixin Pattern

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.

Delegation Pattern

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 by circle because radius got mixed in.

  • Requests for centerX, centerY, and color are delegated to the shape because they don't exist on the circle itself (loosely speaking).

  • A request for any other property returns undefined by definition because it doesn't exist in either the circle or the shape.

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.