Summoning Fundamentals: A Three Part Introduction to OOP in JavaScript for C# Developers - III - Polymorphism
The Mastering the Arcane Art of JavaScript-mancy series are my humble attempt at bringing my love for JavaScript to all other C# developers that haven’t yet discovered how awesome this language and its whole ecosystem are. These articles are excerpts of the super duper awesome JavaScript-Mancy book a compendium of all things JavaScript for C# developers.
In the last two chapters of these OOP mini-series we discussed the pillars of OOP in JavaScript - encapsulation, information hiding and inheritance - and how they differ from what we are accustomed to in C#. In this last chapter we discuss polymorphism and how JavaScript makes it dead simple with the concept of duck typing.
Polymorphism in C#
Polymorphism allows a function to be written to take an object of a certain type T, but also work correctly if passed an object that belongs to a type S that is a subtype of T
Polymorphism is a mechanism that we have in static and strongly typed languages to reuse algorithms or computation on different types. These types must have a type in common by either deriving from a shared base class or by means of implementing the same interface.
Imagine that you are building an army to rule the known world. You have diverse types of minions in this army of the undead (they are cheaper to maintain, skeletons need no food, and ghouls are not really picky about their food):
You can experiment with all examples in this article directly within this jsBin.
public class Skeleton{
public int Health {get;set;}
private Position position;
public Skeleton(){
Health = 50;
position = new Position();
}
public void MovesTo(int x, int y){
position.X = x;
position.Y = y;
}
}
An skeleton that can only move is not a very useful weapon, so you need to give it the ability to attack your enemies:
public class Skeleton{
public int Health {get;set;}
private int damage;
private Position position;
public Skeleton(){
Health = 50;
damage = 10;
position = new Position();
}
public void MovesTo(int x, int y){
position.X = x;
position.Y = y;
}
public void Attacks(Skeleton enemySkeleton){
enemySkeleton.Health -= damage;
}
}
Ok, now you have a skeleton that can attack other skeletons! Yippi! Let’s image that your most bitter enemy has a vast army of goblins:
public class Goblin{
public int Hp {get;set;}
public void MovesTo(int x, int y){
position.X = x;
position.Y = y;
}
}
A Goblin
is not a Skeleton
and therefore your skeletons are going to have a hard time beating that army as of right now. You decide to teach them how to beat goblins up:
public void Attacks(Skeleton enemySkeleton){
enemySkeleton.Health -= damage;
}
public void Attacks(Goblin goblin){
goblin.Health -= damage;
}
And then find out that not only has he goblins, but also orcs, trolls, wargs and wyrms.
public void Attacks(Skeleton enemySkeleton){
enemySkeleton.Health -= damage;
}
public void Attacks(Goblin goblin){
goblin.Health -= damage;
}
public void Attacks(Orc orc){
orc.Health -= damage;
}
public void Attacks(Troll troll){
troll.Health -= damage;
}
public void Attacks(Warg warg){
warg.Health -= damage;
}
public void Attacks(Wyrm wyrm){
wyrm.Health -= damage;
}
And this got out of hand reaaaally fast. This is one scenario in which polymorphism could come in handy. We can take advantage of polymorphism by defining a common base class for all these creatures that would encapsulate the needed contract for being attacked which is the Health
property:
public class Monster{
public int Health {get;set;}
}
public class Skeleton : Monster {}
public class Goblin : Monster {}
public class Orc : Monster {}
public class Troll: Monster {}
// etc
and redefining the Attacks
method in term of that new type:
public void Attacks(Monster monster){
monster.Health -= damage;
}
Of course we would also like our minions to be able to attack defensive structures like towers, or walls, or fences which are most definitely not monsters. In which case it would perhaps be more appropriate to use an interface instead to represent that contract of something being attacked:
public interface IAttackable {
int Health {get;set;}
}
public class Monster : IAttackable{
public int Health {get;set;}
}
public class Skeleton : Monster {}
public class Goblin : Monster {}
// etc
public class Tower: IAttackable {}
public class Fence: IAttackable {}
// etc
Again we redefine the Attacks
method to be even more abstract and applicable to any type that implements the IAttackable
interface be it a creature, a defensive structure, or a sandwich:
public void Attacks(IAttackable target){
target.Health -= damage;
}
In summary, in this example we rewrote the Attacks
method to take advantage of polymorphism so that our Skeleton
can attack anything that implements that IAttackable
interface.
From being able to attack only Skeletons, we went to attacking many types of monsters implemented with many many functions, to reduce that to a single function thanks to polymorphism. The benefits don’t come only from the fact that we have a single function instead of many, but from the increased extensibility of this new solution. Thanks to polymorphism this Attacks
function will work with new creatures, defensive structures, and virtually anything that hasn’t been thought of yet as long as it implements the contract defined by the IAttackable
interface.
Polymorphism in JavaScript
Polymorphism in JavaScript is much simpler than in C#. As a dynamically typed language, JavaScript exhibits what is known as duck typing, whereby an object’s semantics are based on the object’s own methods and properties and not on the inheritance chain or interface implementations (like in C#). This means that if an object has the interface required by a function it will just work. Let’s see JavaScript duck typing with the same example from the previous section:
let skeleton = {
health: 50,
damage: 10,
position: { x: 0, y: 0 },
toString() {
return 'Skeleton'
},
movesTo(x, y) {
this.position.x = x
this.position.y = y
},
attacks(monster) {
monster.health -= this.damage
console.log(`${this} attacks ${monster} fiercely!`)
},
}
When we define the attacks
method as illustrated above, the only thing JavaScript cares about in what regards to monster
is that it has a health
property:
let orc = {
name: 'orc',
health: 100,
toString() {
return this.name
},
}
let goblin = {
health: 10,
toString() {
return 'goblin'
},
}
let tower = {
health: 1000,
toString() {
return 'fortified tower'
},
}
skeleton.attacks(orc)
// =>
skeleton.attacks(goblin)
// =>
skeleton.attacks(tower)
JavaScript doesn’t care about the type of monster
, it only cares about it exposing a matching interface monster.health
. We push this point even further in this example:
skeleton.attacks.health = 50
skeleton.attacks(skeleton.attacks)
// => Skeleton attacks function attacks(monster) { ...
// OMG that was so meta
console.log(skeleton.attacks.health)
// => 40
So, as long as an object has a health
property, it will behave as something that can be attacked as defined by the attacks
method regardless of inheritance. And thus the popular saying that you may have heard before regarding duck typing…
If it walks like a duck, swims like a duck and quacks like a duck, I call it a duck.
If it has a health
property, then it is something that can be attacked. And this is why in previous articles I claimed that inheritance is not a means of polymorphism in JavaScript in the same way that it is in C#. Let’s see a summary of the differences between C# polymorphism and JavaScript duck tiping:
C# Polymorphism | JavaScript Duck Typing |
---|---|
Deriving from a base class or implementing an interface is going to determine whether or not you can use *polymorphism* with a particular type. | In JavaScript it all comes down to having the expected properties or methods when an object is evaluated. |
In C# you establish an explicit expectation about which type can be used within a function in the function's signature. | In JavaScript the expectation is determined by how an object is used within a function implementation. |
In order to take advantage of polymorphism you need to be very intentional about it since it requires a specific architecture in your application. In practice, it means that you'll need to create additional classes or interfaces. | JavaScript duck typing gives you the most granular level of polymorphism with no additional investment required. You don't need to create additional classes or equivalents. |
The intent of the author of the code is very clear since polymorphism only works if the right structures are in place. The extensibility points are very explicit in C#. | In JavaScript any point is extensible. |
Concluding
In this article we did a short review of the concept of polymorphism in C# and how you can use it as a mechanism of code reuse and as a means of creating extensible applications.
You learned how polymorphism works in JavaScript with the concept of duck typing, the idea that an object is not defined by what it is but by what it can do. Thus if something walks like a duck, swims like a duck and quacks like a duck then you treat it like a duck.
We wrapped the article with a brief comparison between C# polymorphism and JavaScript duck typing and how the latter can achieve everything C# can with far less code and a simpler design.
Interested in Learning More JavaScript? Buy the Book!
Are you a C# Developer interested in learning JavaScript? Then take a look at the JavaScript-mancy book, a complete compendium of JavaScript for C# developers.
Have a great week ahead!! :)
More Articles In This Series
- Chapter 0: The Basic Ingredients of JavaScriptMancy
- Chapter 1: The Many a One JavaScript Quirks
- Functions
- Object Oriented Programming
- Basics
- ES6
- OOP
- Introduction to OOP in JavaScript for C# Developers
- Summoning Fundamentals: Introduction to OOP In JavaScript – Encapsulation
- Summoning Fundamentals: Introduction to OOP In JavaScript – Inheritance
- Summoning Fundamentals: Introduction to OOP In JavaScript – Polymorphism
- White Tower Summoning: Mimicking C# Classical Inheritance in Javascript
- White Tower Summoning Enhanced: The Marvels of ES6 Classes
- Black Tower Summoning: Object Composition with Mixins
- Safer JavaScript Composition With Traits and Traits.js
- JavaScript Ultra Flexible Object Oriented Programming with Stamps
- Object Oriented JavaScript for C# Programmers
- Tooling
- Data Structures
- Functional Programming
- And more stuff to be shaped out:
- Functional Programming with JavaScript
- Modules
- Asynchronous Programming
- The JavaScript Ecosystem
Written by Jaime González García , dad, husband, software engineer, ux designer, amateur pixel artist, tinkerer and master of the arcane arts. You can also find him on Twitter jabbering about random stuff.