Thursday, December 17, 2015

Demystifying JavaScript objects - Part 3 of 3

Inheritance needs no introduction in programming world. It's a way by which specialization is achieved in our application. One of the way we achieve DRY principle.

Inheritance in JavaScript is achieved easily, of course less conventional, compared to other high-level programming languages.

In my earlier posts we saw a couple of ways to create classes in JavaScript. Which way should we go about is outside the scope of this post. Certainly intriguing for another post. But when I do get some skin for it, I should also be prepared for some flake. It's a very debatable subject. Although I can say, you'll not be wrong picking any one of the approach. For this post, let's focus on inheritance, I'll share samples for both approaches here, in case you have any of the 2 implementations.

Let's start by with literal notation approach. Here I created a simple "book" object with title, and author properties. Have printInformation method to print title and author information on console. Have overriden toString method for my convenience. (I am sure you can follow all of this from my code. I can stop giving a running commentary on the code and let developers do what they are good at, inspect code for themselves).

 var book = {
 title: "Some book",
 author: "Some author",
 printInformation: function() {
  console.log(this.toString());
 },
 toString: function() {
  console.log("Invoked toString method");
  return "Book: " + this.title + ", Author(s): " + this.author;
 },
 printProperties: function() {
  for(property in this) {
   console.log("Property: " + property);
  }
 }
};

book.printInformation();
var bookPrototype = Object.getPrototypeOf(book);
console.log(typeof(book));

Now that base class is created, lets go ahead creating specialization class, technicalBook (assuming technical here refers to software technology)

var technicalBook = Object.create(book, {
 programmingLanguage: {
  configurable: true,
  enumerable: true,
  writable: true,
  value: 'C++'
 },
 printAllInformation: function() {
  console.log(this.toString() + ', Language: ' + this.programmingLanguage);
 }
});

As you could see, we derived "technicalBook" from "book". We also defined an additional property "programmingLanguage". What's a technical book, without saying which language its on?

We have not also override any base class methods, but we defined a new method "printAllInformation". Along side our declaration of "programmingLanguage" property, we provided additional behavior like configurable, writable, etc on our new property. (Din't see that coming, did you?).

What if I want to Override parent class's methods?
It's simple rename printAllInformation with printInformation. That all you need to do.

What if I want to call parent class method from overridden method?
Keep reading. I have provided some explanation in my samples, next.

I have spoken to a lot of fellow JS developers who have done some good amount of work in JS. There's always a hesitation in them when it comes to code re-use through specialization (Inheritance in particular). If you use React JS, Node JS, this concept will be more than handy. Every good JS developer you look up to are extremely good in inheritance.

Function style Inheritence
Let's turn our attention to function style. Let's redo the same sample we did earlier to maintain context. Your Book class will look like the following.

function Book(title, author) {
 this.title = title;
 this.author = author;
}

Book.prototype = {
 constructor:Book,
 printBookDetails: function() {
  console.log(this.toString());
 }, 
 toString: function() {
  return this.title + " By: " + this.author;
 }
};

TechnicalBook class derived from Books will be defined as follows

function TechnicalBook(title, author, language) {
 this.title = title;
 this.author = author;
 this.language = language;
}

TechnicalBook.prototype = Object.create(Book.prototype, {
 constructor:{
  configurable: true,
  enumerable: true,
  value: TechnicalBook,
  writable: true
 }
});

With derived class defined, we will be able to invoke every base class method from derived class instance, typical to any inheritance pattern. Go ahead create instance of class TechnicalBook and invoke printBookDetails.

Make sure you pass all the needed parameters to TechnicalBook constructor. Parameters to methods and constructors has been a double edged sword in JavaScript. Remember how function overloading works in JavaScript. Same holds good for constructors too. Constructor is nothing more than a function in this case.

In my little experience, I have more often seen developers miss parameter on constructor(s), in the process end up creating half-cooked instances, which adds to over-all problem when it comes to code maintenance. Not to mention the pandemonium during release. we will look into that problem in a little bit or in a different post. 

We have defined derived class, how about adding more functions, and/or calling base class methods.

TechnicalBook.prototype.printDetails = function() {
 // this.printBookDetails();
 Book.prototype.printBookDetails.call(this);
 console.log("Language: " + this.language);
}

With this defined, we will be able to invoke every base class method from derived class. Go ahead, create instance of class TechnicalBook, invoke printBookDetails, see what happens. Again, make sure you pass all needed parameters.

Above code clearly shows a couple of things. We defined a new method printDetails. It's not available in base class. However, we did not want to redo the code in base class, hence we invoked printBookDetails from printDetails. This code tells you not only how to add a new method but also invoke base class method.

We can invoke base class methods in a couple of ways. Direct approach using this keyword is a no-brainer. Other way is using call. Both execute the same method, syntax sugar is different. However, it educates us with a new concept. I'll show that a little later. It's called "constructor stealing". Nerdy way to define child classes that instantiate base class properties. From a daily usage perspective, we could use whatever we are comfortable with.

Next up, how about overriding parent methods?

TechnicalBook.prototype.printBookDetails = function() {
 console.log("Title: " + this.title + ", By: " + this.author);
 console.log("Language: " + this.language);
}

We have printBookDetails defined in base class too. Based on the instance created, appropriate method will be invoked. But wait. Did you notice redundant code between base and derived class?

Its a big taboo in our world. We do everything possible to avoid re-doing things. Isn't that the primary objective for us to do inheritance?.

Easiest way to fix this problem is to invoke base class method, then do our customization. This is where "call" strategy we learnt above comes in handy.

TechnicalBook.prototype.printBookDetails = function() {
 Book.prototype.printBookDetails.call(this);
 console.log("Language: " + this.language);
}

This brings us to the end of this inheritance post. However we will continue to see some more examples on OOP best practices & patterns in JS OOP in my other posts.

You thought I forgot about constructor stealing, parameter handling in constructors? Nope. Not a bit. My next post will be on the best practices, that will talk extensively about them with code samples. Lets stay connected.

No comments:

Post a Comment