Wednesday, December 26, 2012

Javascript Extension Via Constructor

When using Javascript, there are a few ways to inherit or extend object definitions.  Depending on the choices you have made for your prototype declaration, some options make more sense than others.  For this discussion, let's focus on using a function to declare your prototype. This is a pretty common practice and does dictate some other direction if this is where you started.  Let's look at the following declaration:


function Vehicle(numWheels, motor)
{
     this.NumberOfWheels = numWheels;
     this.MotorType = motor;    
};


This is a pretty straightforward object.  We are simply creating a definition for a vehicle based on its number of wheels and the type of motor included.  If we want to extend this prototype and add a function allowing the vehicle to be driven, we would have two possible choices.  We could extend the prototype or simply update the original definition.  It may be best to extend the prototype in order to adhere to good design (e.g. the open/closed principle).  So, we will extend the prototype as follows:

Vehicle.prototype.Drive = function(){document.write("driving from the prototype!<br/>");}


Now our vehicle has the ability to drive via its extended prototype.  Our next step would be to extend the Vehicle object into another object based upon the definition of a Vehicle.  Again, assuming the use of constructor functions for object definitions, we would  start like this.

function MotorCycle(make, model)
{  
     Vehicle.call(this, 2,"V-Twin");
     this.Make = make;
     this.Model = model;
}


The line of code that is giving us the copy of properties is Vehicle.call(this, 2, "V-Twin"); This line is basically grabbing the properties from the Vehicle definition and extending them into the Motorcycle class.  If I were to create a new MotorCycle and output its definition to the document, it would look like this: {"NumberOfWheels":2,"MotorType":"V-Twin","Make":"Honda","Model":"VTX"}".  But, if I were to try to Drive() my new MotorCycle, what would happen?  Nothing.  The Motorcycle doesn't know how to drive since the definition from the prototype has not been applied to the MotorCycle.  In order to have the prototype functions attached to the new object, you have to explicitly set the MotorCycle prototype by default as follows:

MotorCycle.prototype = new Vehicle("2","V-Twin");
MotorCycle.prototype.constructor = MotorCycle;


This gives us access to the functions defined in the prototype of the Vehicle.  The second line appears to have no bearing upon the output or operation of the MotorCycle class.  While this is true at a glance, you have to focus on good design principles ensuring that behavior is what would be expected at every layer of inheritance, a.k.a., the Liskov Substitution Principle.  In Javascript, many people will do type-checking by performing a check against the constructor.  So, in the following statements, the MotorCycle.prototype.constructor = MotorCycle; statement would be the difference between a type check failing or passing.  Without getting into other ways to perform that check, let's just focus on the fact that people will do this:


if( cycle.constructor == Vehicle)
{
   document.write("type check says I am a vehicle");
}
else if( cycle.constructor == MotorCycle)
{
   document.write("type check says I am a motorcycle");
}


Many of the decisions made during the design of the prototype of an object will dictate direction when using the object itself.  In the following code, I have commented out all of the lines that have to do with possible methods of prototyping and extension.  As an exercise, it is interesting to choose varying methods and interact with the prototype by removing the comment from different lines and see how they interact.

function Vehicle(numWheels, motor)
{
     this.NumberOfWheels = numWheels;
     this.MotorType = motor;
//     this.Drive = function(){document.write("driving from the base<br/>");}
};

//Vehicle.prototype.Drive = function(){document.write("driving!<br/>");}


function MotorCycle(make, model)
{  
  //   Vehicle.call(this, 2,"V-Twin");
     this.Make = make;
     this.Model = model;
}


//MotorCycle.prototype = new Vehicle("2","V-Twin");
//MotorCycle.prototype.constructor = MotorCycle;

var cycle=new MotorCycle("Honda","VTX");
document.write(JSON.stringify(cycle)+"<br/>");
cycle.Drive();

if( cycle.constructor == Vehicle)
{
   document.write("old school type check says I am a vehicle");
}
else if( cycle.constructor == MotorCycle)
{
   document.write("old school type check says I am a motorcycle");
}


I have been an object-oriented developer for over 15 years on structured languages such as C++ and C#.  As I spend more time with Javascript, I keep flashing back to a certain meme that sums up my initial reaction to Javascript. 


That is until you really dig in and just spend time trying to break and fix things.  Then, it becomes at least a little bit more clear and can be related back to the knowledge you may already have.

1 comment: