Definition found on WIKI
Dependency injection is a software design pattern that implements inversion of control and allows a program design to follow the dependency inversion principle. The term was coined by Martin Fowler.
An injection is the passing of a dependency (a service) to a dependent object (a client). The service is made part of the client's state. Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.
Means: The dependency should be injected to a object which it needs to complete their unit of work. If object A needs object B to complete its unit of work, then B should be provided to A instead of A creating object B. This also means abstraction should not depend on details but instead details should depend on abstractions.
Let's drill down into this and try to make more sense out of this with examples.
NOTE: All my examples will be using C# language
Lets assume, in my application i need to send updates like email to user when a particular task is complete. So based on the requirement, i wrote a class which sends email messages to the users.
public class EmailNotifier { public void Send(string message) { // code to send email to the user } }
So in my code, i will be using email notifier to send email messages on complete of a task to respective users.
public class Task { // other methods for the task class public void OnComplete(string message) { // notify user with message var notifier = new EmailNotifier(); notifier.Send(message); } }
Now this is all good but we have couple of concerns over here.
Issue: Testing
Since my code has dependency on the external class, we have to make sure that its dependencies are always available, up and running for testing. Plus we cannot do unit testing of our unit of work as we will be testing notifier class and its dependencies too.
Solution:
Instead of creating the instance of the notifier class inside task object, we can pass the object as an parameter in the class. This is also known as Dependency Injection. Doing so, our code will not require to know how to build notifier object, and for testing we can pass the mocked object(s) which simulates different behavior to capture different scenarios.
public class Task { EmailNotifier _emailNotifier; public Task(EmailNotifier emailNotifier) { _emailNotifier = emailNotifier; } // other methods for the task class public void OnComplete(string message) { // notify user with message _emailNotifier.Send(message); } }
With this solution, task class gets the notifier object injected as parameter. Task class is no longer responsible for creating the notifier object.
This is good but it can be still improved by making this loosely coupled. What exactly this means? To understand this, first we need to understand tight coupling.
Tight Coupling:
In computer science, tight coupling (or tightly coupled) is a type of coupling that describes a system in which hardware and software are not only linked together, but are also dependent upon each other. One of the common characteristics of tight coupling are
- A change in one module usually forces a ripple effect of changes in other modules.
- Assembly of modules might require more effort and/or time due to the increased inter-module dependency.
- A particular module might be harder to reuse and/or test because dependent modules must be included.
Loose Coupling:
In computing and systems design, a loosely coupled system is one in which each of its components has, or makes use of, little or no knowledge of the definitions of other separate components.
Better solution would be to pass an abstraction/interface as a dependency. We can create a interface which describe how a Notifier class should look like. Then we will implement different ways of notifiers against this contract like this
public interface INotifier { void Send(string message); } public class EmailNotifier : INotifier { public void Send(string message) { // code to send email to the user } } public class SmsNotifier : INotifier { public void Send(string message) { // code to send sms to the user } } public class EmailAndSmsNotifier : INotifier { public void Send(string message) { // code to send email and sms to the user } }
Now, instead of injecting concrete class, we can inject interface as dependency which promises methods or properties to be present in any class which implements it. This is how new task class will look like
public class Task { INotifier _notifier; public Task(INotifier notifier) { _notifier = notifier; } // other methods for the task class public void OnComplete(string message) { // notify user with message _notifier.Send(message); } }
With new changes we can inject the different types of notifiers user want. In future, any new business need of notifications can been implemented without any major change in the code. We will need to create new class implementing the interface, test it as unit of work and inject it as dependency.
Now the bigger question is, how we inject these dependencies ...
One way is to create factory classes for resolving out dependencies but in any scenario, we will clutter our code with tight coupling.
Other solution is to use DI APIs. They are handy in registering and resolving interface/class types. Registration can been done at the application start. We can update the framework to use our DI object to resolve types in code.
Stayed tuned to my next article to learn more about how to use Unity, DI API in .net applications.
Till then, Happy coding ...
No comments:
Post a Comment