Before to be continue, I suggest that you should know about OOP Design Principles. If you do not any more, please take a look at this link before keep on reading this articles.
One of the design principles is “Dependency Inversion Principle”, it says that:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
So what do these mean? Dependency Inversion Principle requires us decouple high-level modules from low-level ones through an abstract layer between the modules. For example, a software module X needs another module Y. X itself, will initialize and keep an instance of Y. This means Y is an dependency of X, and two modules are depend on each others. To decouple a module from the other, we provide a external module Z that will initialize instance of Y and inject reference of the instance to X. We call this Dependency framework, or Inversion of Control (IoC) framework, framework bases on IoC principle.
The question now is How to implement IoC in software programing world ? As you know IoC is a kind of generic principle, so that there are many ways to implement it. But in my opinion, use what way is less important than the idea of decoupling modules of IoC.
Before move deeply into IoC, let take a look at our sample framework, a Logger system. Basically, there are two modules within the system, the major module was called Logger, and the consumer module was called Client. In a normally programming style, we can also design system like that
And the detail implement will be like that
The above line of codes also do work, except they violate the OOP design principles. The class Client initializes and keeps an instance of Logger inside its constructor. It leads to coupling between them. Beside that, Client aware of the Logger type, so that if we add a new type of Logger, FileLogger for example, then we also have to rewrite Client. That system totally violate the Open Close Principle too.
The real point is class Client controls the object creation, so the idea to avoid tight coupling is abstract the Logger class and shift the control of Logger creation from Client to another entity – an IoC framework. And IoC framework takes control of object creation then passes it to Client class.
To know how to implement IoC framework, I will dig into “Dependency Inception” and “Service Locator” likes the title, of course.
- Dependency Injection(DI)
The basic idea of DI is to have a separating class, a Consumer, to create instance of ILogger, and then passes this object to Client class. There are three kind of DI: Constructor injection, Setter injection, Interface injection.
a. Constructor Injection
With Constructor Injection method, Consumer takes care the Logger creating then passes it to Client object.
Class Client has a property ILogger and a constructor that has ILogger as parameter:
Class Consumer creates real instance of ILogger and also Client:
To use Consumer we write some codes line that:
Although this is an example to demonstrate how to implement Constructor Injection, in fact we should use Factory pattern with Consumer to register and retrieve instance of ILogger. That’s better practice.
b. Setter Injection
As to Setter Injection, Consumer will pass ILogger instance to Client through a Setter method. To receive ILogger, Client will be implemented as below:
Consumer is a quiet similar to Constructor, it creates new instance of ILogger and assign it to Client by Setter method:
And to use Consumer is completely similar to Constructor Injection.
c. Interface Injection
Interface Injection likes Setter Injection. But instead of using default setter method, Interface Injection se an Interface to define explicit setter method. It looks like that:
Source code is similar to Setter Injection, so you could refer to above for implementing. It’s quite easy.
2. Service Locator
The basic idea behind Service Locator pattern is to have a object that contains all instance of ILogger that application need, and returns appropriate one to Client. To implement we provide a ServiceLocator class that contains an ILogger object. Our application has to register indeed instance to ServiceLocator through a setter method, then ServiceLocator can provide this to application whenever one need it. Let see this:
We need only one instance of service locator in whole system so I used Singleton pattern with ServiceLocator class:
In Client we will register and get service, like that:
An extension of Service locator pattern is Service Locator Container pattern. Instead of only one instance of ILogger, Service locator container takes a list of ILogger like its name, a container. You can easily implement a container by yourself, so I don’t post it here.