Next: , Previous: Overriding Methods, Up: Inheritance


2.2.5 Type Checks and Polymorphism

The fact that the API of the parent is inherited is a very important detail. If the API of subtypes is guaranteed to be at least that of the parent, then this means that a function expecting a certain type can also work with any subtypes. This concept is referred to as polymorphism, and is a very powerful aspect of Object-Oriented programming.

Let's consider a dog trainer. A dog trainer can generally train any type of dog (technicalities aside), so it would stand to reason that we would want our dog trainer to be able to train LazyDog, AngryDog, TwoLeggedDog, or any other type of Dog that we may throw at him/her.

img/composition-uml.png

Figure 2.15: Class structure to demonstrate polymorphism

Type checks are traditionally performed in JavaScript using the instanceOf operator. While this can be used in most inheritance cases with ease.js, it is not recommended. Rather, you are encouraged to use ease.js's own methods for determining instance type1. Support for the instanceOf operator is not guaranteed.

Instead, you have two choices with ease.js:

Class.isInstanceOf( type, instance );
Returns true if instance is of type type. Otherwise, returns false.
Class.isA( type, instance );
Alias for Class.isInstanceOf(). Permits code that may read better depending on circumstance and helps to convey the “is a” relationship that inheritance creates.

For example:

    var dog   = Dog()
        lazy  = LazyDog(),
        angry = AngryDog();

    Class.isInstanceOf( Dog, dog ); // true
    Class.isA( Dog, dog );          // true
    Class.isA( LazyDog, dog );      // false
    Class.isA( Dog, lazy );         // true
    Class.isA( Dog, angry );        // true

    // we must check an instance
    Class.isA( Dog, LazyDog ); // false; instance expected, class given

Figure 2.16: Using ease.js to determine instance type

It is important to note that, as demonstrated in Figure 2.16 above, an instance must be passed as a second argument, not a class.

Using this method, we can ensure that the DogTrainer may only be used with an instance of Dog. It doesn't matter what instance of Dog - be it a LazyDog or otherwise. All that matters is that we are given a Dog.

    var DogTrainer = Class( 'DogTrainer',
    {
        'public __construct': function( dog )
        {
            // ensure that we are given an instance of Dog
            if ( Class.isA( Dog, dog ) === false )
            {
                throw TypeError( "Expected instance of Dog" );
            }
        }
    } );

    // these are all fine
    DogTrainer( Dog() );
    DogTrainer( LazyDog() );
    DogTrainer( AngryDog() );
    DogTrainer( TwoLeggedDog() );

    // this is not fine; we're passing the class itself
    DogTrainer( LazyDog );

    // nor is this fine, as it is not a dog
    DogTrainer( {} );

Figure 2.17: Polymorphism in ease.js

It is very important that you use only the API of the type that you are expecting. For example, only LazyDog and AngryDog implement a poke() method. It is not a part of Dog's API. Therefore, it should not be used in the DogTrainer class. Instead, if you wished to use the poke() method, you should require that an instance of LazyDog be passed in, which would also permit AngryDog (since it is a subtype of LazyDog).

Currently, it is necessary to perform this type check yourself. In future versions, ease.js will allow for argument type hinting/strict typing, which will automate this check for you.


Footnotes

[1] The reason for this will become clear in future chapters. ease.js's own methods permit checking for additional types, such as Interfaces.