Chapter 12. Objects and Classes

 

Crude classifications and false generalizations are the curse of organized life.

 
 --George Bernard Shaw (1856–1950)

Organizing the artifacts of your application into appropriate classes is one of the key goals of Object Oriented (OO) Analysis and Design. JavaScript is a fully Object Oriented language, and to achieve its full power we need to take full advantage of its OO features. However, we’ll see that Dojo provides some additional ways to work with the OO feature set of JavaScript that you’ll find really helpful when defining new classes and creating new objects. This chapter explores Objects, the OO features of JavaScript, and the OO enhancements provided by Dojo.

Objects Explained

JavaScript is an object oriented language. An application written in JavaScript exists at run-time as a clamorous conversation of objects interacting with each other through their method calls. Another way to say this is that in JavaScript “EIAO” (Everything Is An Object). This view of an application is different than the procedural approach in which you can think of a program simply as a sequence of instructions (a procedure) to be executed in sequential order with the occasional detour provided by conditional or looping statements. You may not have thought of JavaScript as a fully mature object oriented programming environment but, if you haven’t, now is the time to start.

But what is an object? The classical definition in object oriented design describes an object as a separate entity within an application that implements some behavior and contains some internal representation of its state. A more concrete way to think about objects in JavaScript is to describe them as a sequence of run-time memory containing data and functions that can be referenced and manipulated as a single entity. There are many objects built into JavaScript such as the “document” object, which encapsulates the browser’s internal representation of the current web page (the Document Object Model or DOM) and contains many methods to allow the DOM to be manipulated in some way. But the real power in using objects in JavaScript is to create your own custom objects to represent the various important entities in your application.

Creating Objects

So the first lesson in using objects is to see how you can create objects of your own. There are many ways of creating objects. The simplest way is to create an object using the new keyword.

Although it is the simplest, it is also the least functional. An object is a collection of properties, and in JavaScript these properties can be either pointers to other objects such as arrays and functions or simple properties like strings and numbers. One of the key pillars of an object oriented programming language is its ability to represent an abstraction. For instance, a customer application does not consist of little tiny customers running around inside our computer, but instead it consists of objects representing those customers interacting with each other and with other components in the system.

Encapsulation

Another key pillar of object orientation is encapsulation. This is the ability to wall off an object, separating the inside of the object from the outside world. The inside of an object is known as its implementation. Access to the outside world is provided through methods that are called by other objects. For example, an employee object might contain a method called getAge that returns the age of the employees. But how does the object implement this function? Does it do it by keeping track of the age of the employee as an integer and updating this age annually on the employee’s birthday? Or does the object keep track of the employee’s date of birth and then calculate the employees age each time that the object is asked to getAge? The answer is that “we don’t know and we don’t care.” As long as the getAge method always returns the correct age of the employee, the implementation is not relevant. Over the life of the object, the implementation could even change to use a better algorithm or improve performance. To the outside world, as long as the external interface to the object does not change, any part of the application using the object is unaffected by internal changes in the object’s implementation. And that is the value of encapsulation—to allow the object to present a public face to the world while managing its own private concerns.

Let’s explore how the public face of the object is created. The developer of the object defines properties and methods that will be available in the object. Some properties are simple data types such as strings or numbers. Other properties are more complex and may be pointers to other objects. The methods consist of both the internal functions that the object needs to perform its implementation and the external functions that are available to the outside world so that the object can be used. A simple customer object might have properties such as customerID, "customerName, customerType, and customerStatus. Although it would be possible to change the status of the customer simply by changing the property, we may choose to implement this by hiding the actual property that describes status and use a public method such as updateStatus to allow us to change the way the status is internally represented within the object.

The OO principal of encapsulation defines the technique of hiding some properties and methods from the outside world while exposing others. In many languages this is accomplished through the use of a “public” and “private” keyword, which designates members within the object as being hidden or visible. JavaScript does not implement public and private directly but does provide a technique for hygiene properties and methods within an object. In other words, it is possible to have functions defined within an object that are only callable by other functions within the object and not by external calls to the object. Douglas Crockford provides an excellent description of how to do this on his web site:

Because JavaScript objects are mutable (modifiable), new properties and methods can be added to the object at any time simply by referencing a member and giving it a value. Our first technique for creating objects is to create a new object using the new keyword and then to assign various properties using the dot notation.

        o1 = new Object();
        o1.counter = 0;
        o1.incrementCounter = function() {this.counter++;}

The code here creates a new object containing a single property called counter, which has a starting value of numeric 0. The object also contains a function called incrementCounter, which adds 1 to the current value of counter.

The problem with this approach is that each time we create a new instance of an object type, all the properties and methods must be explicitly assigned. If there are any default values, they must be set. All of the members must be defined, and there is no way to use existing definitions for these properties and methods. This is a fairly unsatisfactory approach for most object oriented developers. Given the size and complexity of most applications today, it is very useful to be able to have a standard template for what a new instance of an object should look like. This provides for an important level of reuse necessary to ensure our productivity.

Object Templates

There are two primary techniques for providing object templates. The first technique is to define the template explicitly as its own entity. A blueprint for how to build the object is created in its own separate file as a class definition. This option is utilized by the Java programming language by allowing developers to create class definitions in their own “.java” files. These template blueprints are very much like the blueprint for a house. When you desire to build a new house, you create the house from an existing blueprint. There’s no limit to how houses you may create from the same blueprint, just as there is no limit to how many objects may be created from the class definition.

Note

Just about every object oriented programming language follows an approach similar to that used by Java. JavaScript is one of the few exceptions.

In Java, after the new object is created from its class definition, the properties and methods defined in that object cannot change. No properties can be added or taken away, and no new methods can be defined or removed. The values of the properties can be changed, of course, but not the existence of the properties. In Java, class definition files are compiled and included with the application at runtime, and they can be used over and over again to create as many instances of an object as the application programmer desires.

JavaScript is not constrained by limitation imposed in Java. JavaScript does not use the class definition technique. JavaScript uses an entirely different approach known as a prototype. Rather than creating a new house from an existing blueprint, an existing house is copied to build a new one. JavaScript objects are built from existing JavaScript objects. You can think of a prototype as acting as the model for the new object.

While the analogy to building objects in Java is like building houses from blueprints, a better analogy for JavaScript is the creation of new objects by copying existing objects. If you copy a 20-page document, your new document will also be 20 pages long, and it will in turn contain all the text and the typos of the original document. To use the prototype technique for creating new objects, it is necessary to have an existing object containing the properties and methods that you want the new object to possess.

Although the prototype process is the technique that JavaScript uses to build new objects, it doesn’t implement it as directly as you might expect. For instance, there is no “clone” or “build from” function or keyword in JavaScript that allows you to directly build a new object from an existing object. Rather the technique is a little more circuitous. To define a prototype to be used as a model for creating new objects, an additional object called the constructor must also be defined. The constructor is a function used to build and initialize new instances of an object for a given data type. The constructor function is called when the new keyword is used in JavaScript. The constructor function can contain assignments for the properties and methods of the new object.

There are still a few problems with this approach. By defining the functions within the constructor, we are repeating code for the functions in every instance of an object that is created from the constructor. Because functions can be rather large and because there is duplication of the exact same function code in each object, this approach provides us with objects that are much larger than necessary. And if we desire to remove a function from each instance of the object or to change the function, it would be necessary to find all the existing objects of that datatype and to explicitly manipulate each one to either remove or change the function. We would also be missing out on one of our key pillars of object oriented programming: inheritance. Inheritance is the ability for an object of one type to inherit properties and methods from an object of their type.

JavaScript Prototypes

Fortunately both of these problems can be solved by using JavaScript prototypes. To understand prototypes, we first have to discuss constructors. A constructor is an object of type function that will be executed when we want to create a new instance of an object. Following is an example of a constructor called DataItem, which can be used to create an unlimited number of new objects of type DataItem.

        function DataItem() {this.counter = 0;};  // Constructor function

        d1 = new DataItem();  // Create a new object of type DataItem

        d1.counter++;  // this increments the counter

In the given code, the object d1 contains the following properties and methods: {counter: 0}.

The secret to using constructor functions is to make sure that the data type following the new keyword is the same as the name of the constructor function. By convention, it is typical to capitalize constructor function names to differentiate them from other functions.

All constructor functions have a property called prototype. This property points to the object to be used as a model for the new object to be built from the constructor. By default this prototype object is a simple object that looks very much like the Object data type. It has a small number of properties and methods. These are the same properties and methods that appear in the Object object (no that isn’t a typo; there is an object of type Object, which all other objects inherit from!). The prototype property of the constructor function is automatically initialized with a pointer to this empty prototype object. But it is simple to override the default prototype object with one of our own. By using the function.prototype reference, we can assign it to an existing object, which will then act as the model for the new object we want to build.

But even using this technique, the prototype is not used to create the new object. It is used to contain properties that will be referenced through the new object. For example, a customer constructor has a reference to a customer prototype that can be used to create as many new instances of customer type objects as desired. We could define all the properties and methods that all customers should have in the customer prototype. You might think that a new instance of customer would have these properties and methods copied into it, but you would be wrong. What actually happens is that a new object is created, and that object has a property that points to its constructor. When a property or method of customer is referenced in the newly created instance, the JavaScript runtime environment takes over. It first looks for the property in the object instance, and if it is there it will use it. But if it is not there, the JavaScript runner works its way up through the prototype chain until it finds the property or gets to the end of the chain.

For example, if the program needs to reference the customerType property in a new customer object, we would use the dot notation c.customerType to reference the property. The JavaScript runner would first look for the property in the customer object, but it would not find it. Then it would use the object’s constructor property to find the constructor function for customer. Then using the constructor functions property called prototype, the JavaScript runner would find the prototype object and see if it had the type property. In our example, customerType would be a property of the customer prototype object.

Sadly, one solution seems to create a new set of problems for us. Different instances of objects of Customer type would have the same constructor function, and that single constructor function would point to a single prototype object. If one instance of an object changed a property in the customer prototype, then all the instances of the customer objects would get that new property. This might not be what we really want to happen. Instead we may want each instance of the object to get its own values of the properties. This can be accomplished by assigning the properties directly in the constructor function using the this keyword to prefix the properties as in this.customerType as the following example demonstrates.

        function Customer() {  // Constructor function
            this.customerType = "RETAIL";
        }

You’ll notice that we still want to keep the function definitions within the prototype because they can be shared between different instances of the Customer objects. One last twist—the prototype objects also have a property called prototype that can be used to point to another object that will behave as the first prototype object does. It will be used by the JavaScript runner to continue to look for properties and methods not defined in the object itself or in the object’s prototype. This is known as prototype chaining.

Using Dojo to Work with Objects

We’ve now reviewed various ways of creating and using objects in JavaScript. But, after all, this is a Dojo book and not simply a JavaScript book. What role does Dojo play, if any, in helping us to use objects? There is a certain amount of complexity in using objects in JavaScript. Your goal may be a simple one: creating a template from which new objects of a particular type may be built. But JavaScript provides many features for doing this from the use of prototypes to constructors to private members. Though JavaScript provides a number of ways to create and use objects, all of them are problematic and inconvenient at best. The developers of Dojo, working to create a JavaScript toolkit, faced the same problems all JavaScript developers do. Luckily, the Dojo developers created something better: an idiom and supporting code for defining classes.

Dojo provides value by giving us a standard idiom for defining classes (the templates from which objects are built). An idiom is a specific syntactic structure for a language. Dojo provides us with this specific structure in the form of a function call with various parameters to allow the definition of a class. We will achieve our goal of creating an object class definition in a single standard Dojo function call. And, drum roll please, that function is dojo.declare!

Dojo Function: dojo.declare

The dojo.declare function is used to create a constructor that can be used to create object with the new keyword. It can also automatically create a prototype with properties and methods. A prototype chain can also be created automatically. This is a very powerful function and is used extensively throughout the Dojo library code. It is also one of the most common functions that you will use in your own application code. So spending some time learning how to use this function properly is certainly time well spent.

Let’s examine a number of typical scenarios that may appear in your application code when you wish to create a new class definition. First let’s look at the simplest possible usage for dojo.declare. We’ll create a constructor for a new type of object that doesn’t even provide any properties, methods, or superclasses for the object.

        dojo.declare("DataItem");

        d1 = new DataItem();

In the preceding code the object d1 contains the following properties and methods:

        {
           "preamble": null,
           "_initializer": null,
           "declaredClass": "DataItem"
        }

Calling dojo.declare creates a constructor function that you can use as you would any normal constructor function. The example shows a constructor named DataItem, which has been created by Dojo. The following code shows the plain JavaScript technique for creating constructors as a comparison to the Dojo technique.

        function DataItem() {};

        d1 = new DataItem();

In this code the object d1 contains the following properties and methods: {}.

The object created using Dojo isn’t exactly the same as the object created through the regular constructor. It contains a few extra properties (preamble and _initializer, which we can ignore for now). It also contains a property called declaredClass, which contains the name of the class we are trying to create. Although this name is the same as the name of the constructor, we shouldn’t think of it that way. Think of it as the name of the class definition.

Defining a Class

Now let’s look at a more complex class definition that contains some properties and methods. We’ll use a Customer type object for our example. We’ll be providing a custom definition of all the members (properties and methods) that a Customer object will have.

   var init = function(name) {
      this.name = name;
      this.status = "ACTIVE";
      this.makeInactive = function() {
         this.status = "INACTIVE";}
      };

   dojo.declare("Customer", null, init);

In the preceding code the object d1 contains the following properties and methods:

   {
   "name": "Tom Jones",
   "status": "ACTIVE",
   "declaredClass": "Customer",
   "preamble": null
    }

This constructor will create a new object with some object specific properties and functions. Unfortunately, there is still a problem. The function makeInactive will exist in each new object that is created. A better way to define the function would be to put it on the prototype for the constructor. This could be done inside the init function by using a reference to "this.constructor.prototype", but Dojo provides a simpler method. The following code illustrates how to add members to the prototype by using the props parameter.

   var init = function(name) {
      this.name = name;
      this.type = "REGULAR";
      this.status = "ACTIVE";
      };

   var props = {
      makeInactive: function() {this.status = "INACTIVE";}
      };

   dojo.declare("Customer", null, init, props);

Now the object is correctly created with the method definition ensconced safely on the prototype object sharing the single copy of the function among all the instances of Customer objects. The props parameter contains an object that has members (both properties and functions) that will be added to the prototype of the constructor only if the member is not already defined in the object.

Superclasses and Inheritance

One of the most important features of an object orientated language is the ability to implement inheritance. Think of inheritance as the power to define a new class by extending an existing class. The existing class is called the superclass, and the new class is called the subclass. We can implement a hierarchy of classes by continuing to extend the subclass by defining its own subclass! The benefit is that we can reuse code that we’ve already written. In the next example, we’ll see how to create a new type of Customer class called RetailCustomer by using our existing Customer data type. Retail Customers have some additional properties to identify the state that taxes them and the discount percentage that we’ll offer them as a special type of customer.

   var props = { discountPercent: 0};

   var init = function( name, taxingState ) {
      // "name" parameter is used by superclass constructor
      this.type = "RETAIL";
      this.status = "ACTIVE";
      this.taxingState = taxingState;
      };

   dojo.declare("RetailCustomer", Customer, init, props);

   c1 = new RetailCustomer("ABC Photos", "IL");

When a new RetailCustomer object is created, the constructor for superclass runs first, and then the init function for the subclass runs. So init can override values provided by superclass. The superclass function is named so because it acts as an inheritance mechanism that can be overridden in the subclass. This provides reuse of the code from the superclass. The init function contains arguments for all the arguments required by the superclass constructor in addition to any it needs for its own work. Why not put the property discountPercent in the constructor and assign it to 0 there? That way would work, but by convention we reserve the constructor for properties that are dependent on what parameters are passed to the constructor when the object is created. So to be more correct, we should really move the assignment of type and status from the init function to the props object.

API for dojo.declare

The following table describes the arguments used when calling the dojo.declare method. The method signature is: dojo.declare(className, superclass, init, props).

Table 12.1. Description of dojo.declare Function

Parameter Name

Parameter Data Type

Description and Usage of Parameter

className

String

Class Name.

The name of the constructor (loosely, a “class”). All classes are concrete. Abstract classes are not supported.

superclass

Function or Array of Functions

Superclass.

If it is an array, the first element is used as the prototypical ancestor, and any following functions become mixin ancestors.

Specify null when there is no superclass. Using null is the equivalent of the no-args default constructor in Java.

init

Function

Constructor Function.

An initializer function called when an object is instantiated from this constructor. Properties can be added in this function by using the this keyword. Since all members are added directly to the new object, use this function primarily for properties and add functions using props. Methods could also be added to the prototype using "this.prototype.functionName" reference, but that is not preferred.

This parameter is not required. If the init parameter passed is not of type Function, it is treated as the prop parameter instead (the declare function rearranges the properties), and then prop is not required.

Why use init? Use init to run some code when the object is created. This is most like the constructor method in Java. This function can also take parameters that would be passed into it when the constructor is called—i.e., new Customer("Tom", 100) would pass two parameters into init function.

When using init with superclasses, be sure and repeat the parameters for the superclass init function as the first parameters to the subclass init function.

props

Object or Array of Objects

Properties object.

An object (or array of objects) whose properties are copied to the created prototype. The created object doesn’t directly have these properties.

The properties are added to the instance of the object created. Any functions are also added to the instance. To add functions to the prototype, add the functions to the array superclass passed as the second parameter.

These are like the member declarations in Java. Properties and methods are declared but not executed when the object is created.

A form of interfaces can be supported by adding properties for other object types here.

Other Dojo Functions

In addition to the dojo.declare function, Dojo also provides some other useful functions for working with objects. If you ever read the source code for the dojo.declare method (and you should), you’ll see that many of these functions are used internally to implement the declare function. But there is nothing to prevent you from using them directly, and they are so useful that you’ll probably find many opportunities to do just that.

Dojo Function: dojo.mixin

Table 12.2. Description of dojo.mixin Function

Method Signature:

dojo.mixin( obj, props)

Summary:

This method adds all of the properties and methods of one object to another object. Only members that are actually in the source property are copied. In other words, members in the prototype are not copied. Also if the target object already has the member, then it is not copied, even if the value in the source object is different.

Parameter: obj

The object that is to be augmented with additional properties and methods.

Parameter: props

An object containing properties and methods that will be added to the object referenced by obj.

Dojo Function: dojo.extends

Table 12.3. Description of dojo.extends Function

Method Signature:

dojo.extends( constructor, props)

Summary:

This method adds all of the properties and methods of one object to the prototype of constructor function. The members are immediately available to any objects already created from the constructor and all future objects to be created from that constructor.

Parameter: constructor

The constructor function whose prototype is to be augmented with additional properties and methods.

Parameter: props

An object containing properties and methods that will be added to the object’s prototype referenced by obj.

Object Graphs and Dot Notation

The next group of Dojo functions are needed to make dot notation easier to use. So we’ll have to discuss dot notation first. And to discuss dot notation, we have to talk about object graphs. Some of the following will be review for many of you, but it never hurts to reinforce the concepts of objects.

As has been said before, in JavaScript applications “Everything Is An Object” (EIAO). For all of us who have drunk of the Object Oriented Kool-Aid (and if you have not, I can get you some!), we believe that everything in the world can be described as an object. Leaving the larger existential question alone, we can probably at least agree that everything in memory during the running of an object oriented application is an object. And that certainly applies to JavaScript. We can think of our page running within the browser as consisting of a large number of objects interacting with each other.

Each object is distinct. This is important as it satisfies one of the primary pillars of object orientation: encapsulation. Encapsulation is the characteristic of allowing a thing to be separate from all other things. Each object exists as its own capsule (that is, it is encapsulated). But encapsulation should only go so far. If objects were so perfectly trapped within their capsules that nothing could get in or out, that they could not see the properties of other objects or that those other objects could not see their properties or run their methods, then our application would be pretty useless. It would be like a box of rocks. Existing in their perfect solid state but not interacting with each other—and nothing useful could come of it.

To achieve some purpose, the objects must interact with each other, and one way of doing this is to refer to each other. Think of object A as having a reference to object B. These references are contained in the properties of objects.

For example, an object representing a Customer may have a property named orders, which would contain a list of references to Order objects. Although each of the Customer objects and each of the Order objects are independent instances of objects, they are related through their references to each other. Imagine the objects as round spheres floating in the application memory space and the properties as cords connecting various objects. Now, in this thought experiment, grab an object and pull on it. What happens? Not only do you retrieve the object you grabbed, but it pulls along all the objects that it references, and they in turn pull various other referenced objects along with them. This bundle of objects you have retrieved is called an object graph. And the single object you grabbed is the root Object. In an object oriented application, there are as many object graphs as there are objects. And each object graph is “like a box of chocolates—you never know what you’re gonna get.” When you pull on an object, you may just get that single object—or that one and a few others. Or maybe every object in the system is somehow connected.

Object graphs can be large, complex, and even self-referential. What happens when an object deep within the graph references an object within its chain or even the root object? But we’ve gone a bit too far. Let’s return to somewhat simple object graphs that aren’t too large and don’t have circular references. How do we use object graphs within a program? How do we reference an object or property deep within the object graph? Let’s answer these questions with an example.

        c1 = new Customer("Tom Jones");
        c2 = new LargeCustomer("ABC Photo");
        c2.subsidiary = c1;

        subName = c2.subsidiary.name;

In this example, the reference c2.subsidiary.name is used to refer to the name property in the subsidiary object referenced by object c2. This is valid syntax, and this example works in JavaScript. However, the problem with using dot notation directly in JavaScript is that if any reference in the chain fails, an error occurs because of a null reference. In other words, the reference to name will fail if the subsidiary property has not yet been assigned. And it fails badly, causing a syntax error and ending execution of any additional JavaScript code.

The fix provided by Dojo for this is to test each reference to make sure an object is there, and when there isn’t, to return a null immediately. The following functions, which make use of dot notation, can now be described.

Dojo Function: dojo.getObject

Table 12.4. Description of dojo.getObject Function

Method Signature:

dojo.getObject(name, create, context)

Summary:

This method returns a property value from an object graph using dot notation. It is useful for long reference chains where it is not certain that each property has a value.

Parameter: name

Path of property in the form “a.b.c”.

Parameter: create

This parameter is optional and may be true or false.

If true, an object is created for each item referenced in the chain where an object does not already exist.

Parameter: context

Optional. When specified, this is an object to be used as the root of the object graph; otherwise, dojo.global will be used as the starting point for the reference.

Dojo Function: dojo.setObject

Table 12.5. Description of dojo.setObject Function

Method Signature:

dojo.getObject(name, create, context)

Summary:

This method sets a property value in an object graph using dot notation. It is useful for long reference chains where it is not certain that each property has a value.

Parameter: name

Path of property in the form “a.b.c”.

Parameter: value

This is the value that the property specified by name will be set to.

Parameter: context

Optional. When specified, this is an object to be used as the root of the object graph, otherwise dojo.global will be used as the starting point for the reference.

Dojo Function: dojo.exists

Table 12.6. Description of dojo.exists Function

Method Signature:

dojo.exists(name, object)

Summary:

This method returns a boolean value based on whether the object referenced by the name parameter using dot notation exists or not. If the object exists, this method returns true otherwise it returns false.

Parameter: name

Path of property in the form “a.b.c”.

Parameter: object

This parameter is optional. When specified, it is used as the starting point for the name reference. Otherwise, dojo.global is used.

Dojo Function: dojo.isObject

Table 12.7. Description of dojo.isObject Function

Method Signature:

dojo.isObject(anything)

Summary:

This method returns a boolean value based on whether the reference passed to the method points to an object. A reference to any object, array, or function will evaluate as true. If the reference is “undefined,” this method will return false.

Parameter: anything

Any reference.

In the next chapter we’ll discover how to work with Strings, one of the most common data types used in most applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.71.115