Dependency injection is a software design pattern that allows removing hard-coded dependencies and making it possible to change them, whether at run-time or compile-time.
Dependency Injection has three kind of software components
1. Dependent or Consumer : Describe what software components, it depends on .
2. Dependencies on which consumer depends
3. Injector also known as provider : Decides what concrete classes satisfy the requirements of dependent object.
What is new In DI: In conventional development the dependent object decide for itself what concrete class it will use. In DI pattern this decision is delegated to the injector which can choose substitute concrete class on run time.
Before plunge into the actual Implementation of DI in scala I would like to discuss when to use this pattern and what are the advantages of this pattern.
As Object Oriented Language Coder we all are familiar with Inheritance. But Here we would focus on Composition.
1. Inheritance : It generally provides two main abstractions
a. Dynamic Binding – JVM will decide which method implementation will invoke at runtime.
b. Polymorphism – Variable of Super Class Type can be used to hold the reference of it’s subclass.
Let’s Explain this concept using the simple example .
Class Vehicle is base class of car.
Vehicle has a method fuleType.
Def fuleType = “Diesel”
Now car extends the vehicle class and override it’s method as follows
def fuleType = “petrol”
Now we have two implementations of fuleType , at run time it is the responsibility of jvm to decide which implementation will be invoked at runtime. This is known as dynamic binding.
Class bike also extends the class Vehicle. We can use vihicle type object to hold car as well as bike.
Val vehicle = new Car
val vehicle = new Bike
This phenomena is known as polymorphism.
Problem with Inheritance Relationship
In an inheritance relationship, superclasses are often said to be “fragile,” because one little change to a superclass can ripple out and require changes in many other places in the application’s code. Here coupling of base class and sub class is very strong. Changes to the superclass’s interface, however, can ripple out and break any code that uses the superclass or any of its subclasses. What’s more, a change in the superclass interface can break the code that defines any of its subclasses.
In the above example if we add another subclass Bus , there would not be any problem but if you change return type of fuleType method to String to Integer. you can break the code that invokes that method on any reference of type vehicle. In addition, you break the code that defines any subclass of vehicle that overrides the method. Such subclasses won’t compile until you go and change the return value of the overridden method to match the changed method in superclass vehicle.
Inheritance is also sometimes said to provide “weak encapsulation,” because if you have code that directly uses a subclass, such as car, that code can be broken by changes to a superclass, such as vehicle.
2. Composition: Given that the inheritance relationship makes it hard to change the interface of a superclass, it is worth looking at an alternative approach provided by composition. It turns out that when your goal is code reuse, composition provides an approach that yields easier-to-change code.
In the composition approach, the subclass becomes the “front-end class,” and the superclass becomes the “back-end class.” With inheritance, a subclass automatically inherits an implemenation of any non-private superclass method that it doesn’t override. With composition, by contrast, the front-end class must explicitly invoke a corresponding method in the back-end class from its own implementation of the method. This explicit call is sometimes called “forwarding” or “delegating” the method invocation to the back-end object.
The composition approach to code reuse provides stronger encapsulation than inheritance, because a change to a back-end class needn’t break any code that relies only on the front-end class.
What to choose Inheritance or Composition
If you find “is a “ relationship then go with inheritance.
Car “ is a “ Vehicle
Bus “is a” Vehicle
But if relationship is “has a “ type then go with composition.
Car “has a “ Engine”
Car “has a “ fule tank”
Self Type Annotation:
In scala there are various way to achive compositions. But here I will discuss how to use self-type-annotations for composition.
There is a class engine. Class vehicle needs this class to complete its execution. Here we are not going to inherit the engine as superclass of vechile. We are looking to achive code usability using composition.
“this:Engine =>” is known as self-type-annotation. Using this it is possible to use start method of class engine from class Car.
Suppose car has more dependencies like PowerWindow, FuleTank etc than we can use annotation like
this:Engine with PowerWindow with FuleTank
Now we know what is composition and how and where to use composition. It’s time to Implement DI in scala.
Here we have three classes Engine, Tank and Car. Class Car depends on Tank and Engine to complete it’s execution. Engine and Tank are the dependencies for Car.
Now Encapsulate the classes in component traits as follows.This simply creates a component namespace for our classes. Dependencies can be injected in Car using self type annotation.
One of the beauties here is that all wiring is statically typed. For example, if we have a dependency declaration missing, if it is misspelled or something else is screwed up then we get a compilation error. This also makes it very fast.
But still there is a problem, We have strong coupling between the service implementation and its creation, the wiring configuration is scattered all over our code base; utterly inflexible.
Let’s fix it.
Instead of instantiating the services in their enclosing component trait, let’s change it to an abstract member field.
By doing this switch we have now abstracted away the actual component instantiation as well as the wiring into a single “configuration” object.