ref – https://stackoverflow.com/questions/20058391/javascript-dependency-injection
Problem
So we have a Printer class with a prototype function ‘print’. It prints something to console depending on a public property in the constructor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Printer { constructor () { this.lcd = ''; } /* umm! Not so flexible! */ print (text) { this.lcd = 'printing...'; console.log (`This printer prints ${text}!`); } } // Usage: var printer = new Printer (); printer.print ('hello'); |
It’s not flexible because it ONLY has this feature of printing. The functionality can’t be switched out.
In order to make it more flexible:
1) we use a driver property to take on new classes with different printing functionalities.
2) Then in our print function, we simply call the functionalities.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class Printer { constructor () { this.lcd = ''; // more flexible, but still hard coding this.driver = new Driver (); // 1) } print (text) { this.lcd = 'printing...'; this.driver.driverPrint (text); // 2) } } class Driver { driverPrint (text) { console.log (`I will print the ${text}`); } } // Usage: var printer = new Printer (); printer.print ('hello'); |
In this example, we can just add/remove functionalities in Driver. As long as the name driverPrint is being executed in Printer, then we’re good.
So our Printer class is now more modular, clean and easy to understand but there’s still some inflexibility yet again.
Any time you use new keyword you are actually hard-coding something
In this case you are constructing a driver inside your Printer which in real world is an example of a printer that comes with a built-in driver that can never change!
A better version is to inject a driver at the time we construct a printer meaning you can make any type of printer, color or black&white, because this time the driver is being made in isolation and outside the Printer class and then given (INJECTED!) into the Printer…
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 30 31 |
class Printer { constructor (driver) { // inject driver here this.lcd = ''; this.driver = driver; } print (text) { this.lcd = 'printing...'; this.driver.driverPrint (text); // call on interface } } // implement the interface driverPrint class BWDriver { driverPrint (text) { console.log (`I will print the ${text} in Black and White.`); } } // implement the interface driverPrint class ColorDriver { driverPrint (text) { console.log (`I will print the ${text} in color.`); } } // Usage: var bwDriver = new BWDriver (); var printer = new Printer (bwDriver); printer.print ('hello'); // I will print the hello in Black and White. |
The problem presented here is that we are creating a new printer each time we need our printer to print with a different driver! That is because we are passing our driver of choice to the Printer class at the construction time.
or example if now I want to do a color print I need to do:
1 2 3 |
var cDriver = new ColorDriver (); var printer = new Printer (cDriver); // Yes! This line here is the problem! printer.print ('hello'); // I will print the hello in color. |
Provide Setter
Providing a setter function can dynamically let you switch out different Drivers, thus switching up on your functionalities.
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 30 31 32 33 34 35 36 37 |
class Printer { constructor () { this.lcd = ''; } setDriver (driver) { // setter function can take on different drivers this.driver = driver; } print (text) { this.lcd = 'printing...'; this.driver.driverPrint (text); } } class BWDriver { driverPrint (text) { console.log (`I will print the ${text} in Black and White.`); } } class ColorDriver { driverPrint (text) { console.log (`I will print the ${text} in color.`); } } // Usage: var bwDriver = new BWDriver (); var cDriver = new ColorDriver (); var printer = new Printer (); // I am happy to see this line only ONCE! printer.setDriver (bwDriver); printer.print ('hello'); // I will print the hello in Black and White. printer.setDriver (cDriver); printer.print ('hello'); // I will print the hello in color. |
Dependency Injection using Duck Typing
ref – https://stackoverflow.com/questions/12762550/example-of-javascript-duck-typing
Functions in JavaScript are versatile. They can be used as subroutines, methods, constructors, namespaces, modules, and much more.
The reason people advise against using prototype chain inheritance (by using instanceof) in JavaScript is because it hides the true power of JavaScript. Functions are just as expressive if not more expressive than objects. This has been proven by Alonzo Church who’s work, Lambda Calculus, is Turing Complete.
To answer your question directly I’ll use functions to create a Turtle, a Lion and a Dolphin. Then I’ll demonstrate how a turtle is an OceanAnimal and a LandAnimal, how a lion is only a LandAnimal, and how a dolphin is only an OceanAnimal. I’ll conclude by explaining what is duck typing.
First let’s create the constructor for an OceanAnimal:
1 2 3 4 5 6 |
function OceanAnimal() { this.swim = function (n) { return "I am " + this.name + ", the " + this.type + ", and I just swam " + n + " meters."; }; } |
Next we’ll create the constructor for a LandAnimal:
1 2 3 4 5 6 |
function LandAnimal() { this.walk = function (n) { return "I am " + this.name + ", the " + this.type + ", and I just walked " + n + " meters."; }; } |
Next we’ll create the constructor for a LandAnimal:
1 2 3 4 5 6 |
function LandAnimal() { this.walk = function (n) { return "I am " + this.name + ", the " + this.type + ", and I just walked " + n + " meters."; }; } |
What’s happening here?
Okay, we want Turtle to inherit from both OceanAnimal and LandAnimal. So we’re calling LandAnimal.call(this) and OceanAnimal.call(this).
In this way we’re using the OceanAnimal and LandAnimal constructors as mixins. Thus Turtle inherits from both OceanAnimal and LandAnimal without actually becoming of type OceanAnimal or LandAnimal.
Connecting through prototype chain can work but it will take more work via connecting their prototypes
Another thing to notice is that we’re setting the type property on the prototype of Turtle instead of inside it. This is because type is the same for all turtles. Thus it’s shared. The name of each turtle on the other hand may vary and hence it’s set inside the constructor.
1 2 3 4 5 6 |
Lion.prototype.type = "lion"; function Lion(name) { this.name = name; LandAnimal.call(this); } |
Since Lion is a LandAnimal we only mix in the LandAnimal constructor.
Similarly for a Dolphin:
1 2 3 4 5 6 |
Dolphin.prototype.type = "dolphin"; function Dolphin(name) { this.name = name; OceanAnimal.call(this); } |
Now that we have created all the constructors let’s create a turtle, a lion and a dolphin:
1 2 3 |
var yoyo = new Turtle("Yoyo"); var simba = new Lion("Simba"); var dolphy = new Dolphin("Dolphy"); |
Awww, now let’s set them free:
1 2 3 4 |
alert(yoyo.walk(10)); alert(yoyo.swim(30)); // turtles are faster in the water alert(simba.walk(20)); alert(dolphy.swim(20)); |
Okay, so what’s duck typing? We know that yoyo is an OceanAnimal and a LandAnimal. However if we do yoyo instanceof OceanAnimal or yoyo instanceof LandAnimal then it returns false. What?
You: Stupid JavaScript. A Turtle is an OceanAnimal and a LandAnimal!
JavaScript: Not from where I’m standing. All I know is that it’s a Turtle.
You: But if it swims then it’s an OceanAnimal, and if it walks then it’s a LandAnimal.
So since JavaScript is such a party pooper we’ll have to create our own test to check if an object is an OceanAnimal and if it’s a LandAnimal.
Let’s start with an OceanAnimal:
1 2 3 4 5 |
function isOceanAnimal(object) { if (typeof object !== "object") return false; if (typeof object.swim !== "function") return false; return true; } |
Similarly, for a LandAnimal:
1 2 3 4 5 |
function isLandAnimal(object) { if (typeof object !== "object") return false; if (typeof object.walk !== "function") return false; return true; } |
So now we can use isOceanAnimal(yoyo) instead of yoyo instanceof OceanAnimal, and isLandAnimal(yoyo) instead of yoyo instanceof LandAnimal; and both these functions will return true for our beloved yoyo. Yay!