Polymorphism in depth.
Article assumes user to have some experience in Object Oriented Programming.
The article uses Dart as a programming language for example.
To understand polymorphism, let’s take an example of a Laptop.
Suppose you have a laptop with USB ports.
What are the things that you can insert into the USB ports ?
The answer is anything that has a USB male connector.
Can it be a mouse ? YES.
Can it be a keyboard ? YES.
Can it be a printer ? YES.
What’s common in these devices ?
They are I/O devices.
They are used for input and output.
Now, programmers realized very quickly that it was of our interest to have our systems (computers) be independent of I/O devices.
Creation of this independence is called polymorphism.
In our example USB was an interface between our laptop and the I/O devices.
Now, how do we do that in code ?
Consider the following example :
Problem
Let’s say that module A
calls module B
.
We can consider module A
to be our laptop and module B
to be our keyboard.
In this scenario, the code will look as follows :
Here, ModuleA
knows about ModuleB
because the code in module_a.dart
explicity has a declaration of ModuleB
in relative line number 11.
The following will be the dependency graph :
This means ModuleA
is tightly coupled to ModuleB
in compile time.
So, if ModuleA
has to compile then it must have ModuleB
compiled too. That’s bad.
Why’s this bad ?
Back in our example, this means that when our laptop was made it will only support a particular kind of keyboard designed for that laptop but we want any keyboard to work with our system.
Solution
To solve this, we create an interface between ModuleA
and ModuleB
so that ModuleA
doesn’t need to directly reference ModuleB
. This can be done because ModuleA
depends upon the function doSomething()
present in ModuleB
.
Now,
In module_a.dart
there is no declaration of ModuleB
directly, instead it has a reference to some Interface
.
Now, the dependency graph is like :
If we look at interface.dart
we’ll see that there is no implementation of the method doSomething()
but ModuleA
is able to call the function doSomething()
in relative line number 4, in module_a.dart
.
Notice, that ModuleB
is no longer referenced in ModuleA
anywhere in compile time but we can inject ModuleB
into ModuleA
at runtime via the constructor.
Why is this good ?
It’s good because, this means that now we can have a ModuleX
that implements the Interface
which can work with ModuleA
.
This gives the developer immense power.
This means that we can change the behavior of ModuleA
without ever changing it’s source code.
We can inject the dependencies as follows :
Here, ModuleA
accepts ModuleX
as well as ModuleB
.
But, when we look at the output :
The behavior of ModuleA
is different for the same function call.
This is why we like Polymorphism.
It gives the developer immense power.