Inheritance

Contact Us or call 1-877-932-8228
Inheritance

Inheritance

One of the real benefits of object-oriented programming is the ability to build upon existing classes - to inherit all of the functionality of a well-written class and extend it for some purpose specific to your needs.

Imagine that you have a Person class - a complex set of code that includes all of the functionality you need, that has been tested in production for years, but yet which is missing just a few things that you need for a new application. Everything is great - you just need to add a field for "EmployeeID" or something similar, but the rest of the class works fine. In JavaScript (like in many other languages) we would here create a subclass using the extends keyword; note that this keyword is new to ES2015. Let's look at a simple example to see how this works:

Code Sample:

AdvancedObjects/Demos/pets.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>Pets</title>
	<script>
		class Pet {
			constructor(name, color) {
				this.name = name;
				this.color = color;
				this.animaltype = 'animal';
			}
			toString() {
				return this.name + ' - a ' + this.color + ' ' + this.animaltype;
			}
			speak() {
				return 'ehh';
			}
		}

		class Dog extends Pet {
			constructor(name, color) {
				super(name, color);
				this.animaltype = 'dog';
			}
			speak() {
				return 'woof';
		    }
		}

		             class Bird extends Pet {
			constructor(name, color, wingspan) {
				super(name, color);
				this.animaltype = 'bird';
				this.wingspan = wingspan;
			}
			toString() {
				return this.name + ' - a ' + this.color + ' ' + this.animaltype + ' with wingspan of ' + this.wingspan + ' centimeters';
		    }
			speak() {
				return 'cheep';
			}
		}
	</script>
</head>
<body>
	<h1>Pets</h1>
	<script>
		var pet = new Pet('Cuddles', 'brown');
		document.write(pet);
		document.write('<br>');
		document.write(pet.name + ' says "' + pet.speak() + '"');
		document.write('<hr>');
		var fido = new Dog('Fido', 'brown');
		document.write(fido);
		document.write('<br>');
		document.write(fido.name + ' says "' + fido.speak() + '"');
		document.write('<hr>');
		var feathers = new Bird('Feathers', 'yellow', 7);
		document.write(feathers);
		document.write('<br>');
		document.write(feathers.name + ' says "' + feathers.speak() + '"');
	</script>
</body>
</html>

Code Explanation

We define class Pet with properties name (the name of the pet) and color - both set when the constructor is called - as well as animaltype, which we always set to the value 'animal'. Our Pet class includes a toString() method (returning something like "Cuddles - a brown animal") and a speak() method. All instances of class Pet speak "ehh".

The Pet class, while simple, works great. We can instantiate a Pet object, write it to the screen (implicitly invoking its toString() method), and ask it to speak().

But the generic Pet class won't serve our needs for more-specific types of pets: we might want to keep track of the wingspan of a pet which is a bird, but it wouldn't make sense to add a property to the Pet class for wingspan, since for most types of pets "wingspan" would be irrelevant.

Thus we turn to inheritance: we create two more-specific classes (Dog and Bird), each of which handles data and behavior appropriate for a particular kind of pet. Note that both use the keyword extends in their definition, to indicate that each class is a subclass of (inherits from) Pet.

The Dog class constructor invokes the parent-class (Pet) constructor by calling super(name, color) on line 23. Since any Dog object is also a Pet object, calling super(name, color) has the effect of setting this.name = name and this.color = color. Dog objects inherit the properties and methods of their parent, so any Dog has both a name and a color. In the Dog constructor we set this.animaltype to 'dog'.

We don't need to modify the toString() method for the Dog class; since we set any object of class Dog to have animaltype 'dog', the toString() method for Dog will return something like "Fido - a brown dog".

We override the default speak() method for the Dog class: where any Pet would say "ehh", any Dog would say "woof".

We do similar things in the Bird class - also a subclass of Pet. In addition, we add a property (wingspan) specific to birds, and override the toString() method to include the wingspan information - something like "Feathers - a yellow bird with wingspan of 7 centimeters".

Note that, when we instantiate an object (feathers) of class Bird on line 59, we include an extra parameter for the wingspan.

Let's review another example, in which we modify our Person class to include a team jersey number of members of our team.

Code Sample:

AdvancedObjects/Demos/multipleObjectsInheritance.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>Multiple Classes with Inheritance</title>
	<script>
		class Person {
			constructor(fname, lname, age) {
				this.fname = fname;
				this.lname = lname;
				this.age = age;
			}
			toString() {
				return this.fname + ' ' + this.lname;
			}
		}

		class Player extends Person {
			constructor(fname, lname, age, jerseyNumber) {
				super(fname, lname, age);
				this.jerseyNumber = jerseyNumber;
			}
			toString() {
				return super.toString() + ' (#' + this.jerseyNumber + ')';
			}
		}

		class Team {
			constructor(teamName, sport) {
				this.teamName = teamName;
				this.sport = sport;
				this.players = [];
			}
			addPlayer(person) {
				if (person.age >= 18) {
					this.players.push(person);
					return true;
				}
				return false;
			}
			toString() {
				var str = '<strong>' + this.teamName + '</strong>';
				str += '<ul>';
				for (let p in this.players) {
					let person = this.players[p];
					str += '<li>' + person + '</li>';
				}
				str += '</ul>';
				return str;
			}
		}
	</script>
</head>
<body>
	<h1>Multiple Classes with Inheritance</h1>
	<script>
		var jdoe = new Person('Jane', 'Doe', 32);
		var mcontrera = new Player('Maria', 'Contrera', 47, 2);
		var afung = new Person('Adam', 'Fung', 23);
		
		var Tigers = new Team('The Tigers', 'soccer');
		if (!Tigers.addPlayer(jdoe)) {
			alert('player not added - too young');
		}
		if (!Tigers.addPlayer(mcontrera)) {
			alert('player not added - too young');
		}
		if (!Tigers.addPlayer(afung)) {
			alert('player ' + afung + ' not added - too young');
		}

		document.write('Here is my team:<br><br>');
		document.write(Tigers);
	</script>
</body>
</html>

Code Explanation

Our Person class worked great to represent the people we added to an object of class Team. But, after thinking on it some more, we decided it would make sense to include a jersey number for each team member. Adding a jerseyNumber field to class Person didn't really make sense - it isn't really applicable to generic "people" - so creating a subclass Player seemed the best way to go.

Thus we create class Player which inherits from class Person. Any object of class Player includes the same (fname, lname, and age) properties as a Person, but now includes a new, specific property jerseyNumber. Class Player overrides class Person's constructor to allow for the setting of this property, and overrides the toString() method to display the player's jersey number.

Note that we create two Person objects (jdoe and afung) and one Player object (mcontrera) and add each to the team. Our Team object, Tigers, is happy to accept any flavor of object (Person or Player) to be added as a team member. Interestingly, when we build the unordered list of players inside the Team-class toString() method, JavaScript "knows" which toString() method to use, Person or Player: Maria Contrera's bullet shows her jersey number, while the other two team members' bullets do not.

Next