Monday, January 11, 2016

Class style programming in JavaScript: Part 2

In Part 1 of this post we explored the class style paradigm for developing modular code. I encourage reading it before continuing. Its ok if you dont want to, here is the tldr; bullet-point version:
  • Classes are not a language construct in JavaScript
  • Class-style programming can be simulated because of the language's flexibility
  • Function names are capital-cased but the upper-casing is convention-only i.e. to make them look like Java's class constructors. 
    • var Car = function() {};
  • Invoking these functions with the new keyword creates objects/instances
    • var myCar = new Car();
  • A prototype property exists on all functions. It is an object.
    • console.log(Car.prototype); // Object {}
  • Code(data/functions) that is meant to be shared across these different instances are added to the prototype property
    • Car.prototype.drive = function() { //do stuff };
All that said, we have to admit that the using the prototype property to achieve class-style programming feels nothing like using Java's classes. But that was the intent when the feature was added to the language i.e. to make it more familiar to Java programmers.
In this post, we will try to understand how we would achieve code re-use similar to Java's Class-based inheritance pattern. 
Continuing with our Car example .. if we needed a specialized version we could call it NextGenCar which essentially built upon the existing features. How would we model these requirements where NextGenCar needs the following:
  1. Its constructor to re-use Car's constructor and initialize additional instance properties
  2. Inherit all of Car's features
  3. to drive more efficiently than the current drive i.e. it needs to extend drive()
  4. to have a completely overhauled braking system i.e. it needs to override stop()
  5. to have a way to switch to driverless mode and back 
The code solution below is annotated with comments on how each of the above requirement is satisfied
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var NextGenCar = function(numGears, numWheels){
   // #1: Use the call method thats available on functions to mimic super constructor call
   Car.call(this, numGears); 
   // #1 Initialize additional properties
   this.numWheels = numWheels; 
}

// #2: Using Car's prototype as blue-print create a new object 
// and set it as NextGenCar's prototype 
// so that it inherits all methods of Car via Object [[Prototype]] linkage
NextGenCar.prototype = Object.create(Car.prototype);

NextGenCar.prototype.drive = function() {
   //#3: Use the call method to mimix a super call to the method on the "super-class"
   Car.prototype.drive.call(this); 
   console.log('more for the gallon'); 
}

NextGenCar.prototype.stop = function() {
   console.log('you said stop and i stopped before you finished saying'); // #4
}

// #5: New methods
NextGenCar.prototype.switchMode: function(mode){
   console.log('cruising in '+mode+' mode');
}

var myNextGenCar = new NextGenCar(4, 4);
myNextGenCar.switchMode('driverless'); //cruising in driverless mode'
I think the above code is definitely hard to understand and barely represents what we are trying to do.
Lets see if a picture helps..



(the dotted arrows indicate the prototype linkage between objects)

Did the picture help ?
maybe.. a little bit. But I think this exercise helps realize the futility of attempting class-style programming in a class-less world. There are better alternatives - Object Programming using prototypes and/or functional programming. I will explore the former in my next post.

All that said, it is still useful to understand constructor functions and the prototype property on functions because these are deeply entrenched in the language. There is also a class of problems that can be solved elegantly using ES6 classes only  and not using the alternatives mentioned above (and this will be a topic of a blog post for an other day)

References


No comments:

Post a Comment