Introduction to Dependency Injection
- Processes, standards and quality
- Technologies
- Others
The letter ,,D” is abbrevation of „Dependency Inversion” which means:
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend upon details. Details should depend upon abstractions.
Another importan term is IoC (Inversion Of Control).
In software engineering, inversion of control (IoC) describes a design in which custom-written portions of a computer program receive the flow of control from a generic, reusable library. A software architecture with this design inverts control as compared to traditional procedural programming: in traditional programming, the custom code that expresses the purpose of the program calls into reusable libraries to take care of generic tasks, but with inversion of control, it is the reusable code that calls into the custom, or problem-specific, code.
After a closer look at „Inversion Of Control” you will find out that there are three approaches to do that:
- dependency injection (DI)
- events
- aspects
In the mind map you can see terms related to dependency injection which I’m going to describe in the series of articles. I must admit that I was not aware of the complexity of this problem until I’ve read „Dependency Injection in .NET”. I strongly recommend reading this book. It was written in a simple way and is full of good examples. You can also read manual for Unity framework.
At first we should consider if our code should have some dependencies. If it doesn’t require any dependencies, then using DI doesn’t make any sense. If we introduce dependencies to code which doesn’t require them, it will deteriorate its readability and testability.
Generally our code:
- does not have to have any dependencies
- depends on classes for which dependency injection does not make any sense „stable dependencies” (we use „new” operator to instantiate them, e.g. we do not expect to replace StringBuilder by our own implementation)
- depends on classes for which dependency injection make sense „volatile dependencies”. (for them we use one of the DI patterns described below)
Criteria of stable dependencies:
- Class or module already exist.
- New version will not introduce any changes which could cause compilation errors.
- You will not change implementation for another.
- Queries returns deterministic values.
Criteria of volatile dependencies:
- Dependency require configuration for runtime environment (e.g web services, file system).
- Implementation of dependency hasn’t been created yet.
- Dependency cannot be installed on all machines in organisation (for some reason: costs or licence)
- Queries returns non deterministic values (which does not allow you to write unit tests).
Dependency Injection Patterns
Now we know what we should inject, but there is another question how to do it? Dependency Injection patterns provide answer for this question. In this article I am going to describe four of them.
Constructor Injection
Constructor Injection – this pattern should be used when a class requires one or more dependencies and there are not any good implementation of these dependency in the same module (Local Default).
All dependencies are passed to a client class constructor. Because all dependencies are required, we should check them against nulls. This pattern should be your first choice.
//abstraction of dependency interface Dependency{} //implementation of abstraction class ImplementationOfDependency : Dependency {} class Client { private readonly Dependency dependency; //pass implementation of dependency by constructor's parameter public Client(Dependency dependency) { //check against null if(dependency == null) { throw new ArgumentNullExceptoin("dependency"); } this.dependency = dependency; } }
Property Injection
Property Injection – this pattern should be used when there is a good implementation of dependency from the same module (Local Default), but we want to leave possibility to provide different implementation of dependency.
Providing implentation of dependency is optional because if dependency is not set then Local Default is used. In the implementation listed below, we have to use property to get reference to implementation of dependency in Client class body.
//abstraction of dependency interface Dependency { } //implementation of abstraction class FirstImplementationOfDependency : Dependency { } class SecondImplementationOfDependency : Dependency { } class Client { private Dependency dependency; public Dependency Dependency { get { if (this.dependency == null) { //set default implementation of dependency this.dependency = new FirstImplementationOfDependency(); } return this.dependency; } set { if (value == null) { throw new ArgumentNullException("dependency"); } this.dependency = value; } } } class Application { public static void Main(string[] args) { //this instance use default implementation of dependency Client fistInstanceOfClient = new Client(); Client secondImplementationOfClient = new Client(); //set different implementation of dependency secondImplementationOfClient.Dependency = new SecondImplementationOfDependency(); } }
Method Injection
Method Injection – sometimes you have to provide different implementation of dependency for method call. In such situation you should use Method Injection pattern.
In Method Injection providing implementation of dependency is not optional. We have to pass implementation of dependency in every method call.
//abstraction of dependency interface Dependency { } //implementation of abstraction class FirstImplementationOfDependency : Dependency { } class SecondImplementationOfDependency : Dependency { } class Client { //pass dependency as a method parameter public void Do(int firstParameter, Dependency dependency) { //check against null if (dependency == null) { throw new ArgumentNullException("dependency"); } //use dependency ... } } public class Application { public static void Main(string[] args) { int someValue = 1; Dependency firstImplementationOfDependency = new FirstImplementationOfDependency(); Dependency secondImplementationOfDependency = new SecondImplementationOfDependency(); Client client = new Client(); //pass FirstImplementationOfDependency as implementation of Dependency client.Do(someValue, firstImplementationOfDependency); //pass SecondImplementationOfDependency as implementation of Dependency client.Do(someValue, secondImplementationOfDependency); } }
Ambient Context
Ambient Context – if some dependency is used by many classes you could create static accessor for such dependency, which makes it reachable for all clients. We should use this pattern for true Cross-Cutting concerns, where patterns described below does not fit, and we do not want to pass dependencies to classes which do not require it.
While creating Ambient Context you should keep in mind that it does not return different type of services on demand. It should be implemented as an abstract class and declare virtual methods or properties required by clients. Such methods or properties should do some calculation but do not return instance of some interface. Ambient Context cannot return null, that is why Local Default is set as a current value. Another thing which we should keep in mind is that Ambient Context should be safe for threads. Current value can be stored in static field, stored in local thread storage or can be binded to some context (e.g. web request).
For example, time is required by many classes from many layers, so this is true cross-cutting concern.
abstract class TimeProvider { //current dependency stored in static field private static TimeProvider current; //static property which gives access to dependency public static TimeProvider Current { get { if (current == null) { //Ambient Context can't return null, so we assign Local Default current = new DefaultTimeProvider(); } return current; } set { //allows to set different implementation of abstraction than Local Default current = (value == null) ? new DefaultTimeProvider() : value; } } //service which should be override by subclass public virtual DateTime Now { get; } } //Local Default class DefaultTimeProvider : TimeProvider { public override DateTime Now { get { return DateTime.Now; } } }
Here is an example how you can use ambient context.
public class Application() { public static void Main(string[] args) { Console.WriteLine(TimeProvider.Current.Now); } }
If our tests require specific value of current time we could do the fallowing:
class TestTimeProvider : TimeProvider { private DateTime nowDateTime; //default constructor provides default value for service public TestTimeProvider() { this.nowDateTime = DateTime.Now; } public override DateTime Now { get { return this.nowDateTime; } } //Setter for value returned by Now property public void SetNowDate(DateTime nowDateTime) { this.nowDateTime = nowDateTime; } } [TestFixture] class TestClass { [Test] public void TestSomething() { DateTime nowDate = DateTime.Now; TestTimeProvider testTimeProvider = new TestTimeProvider(); testTimeProvider.SetNowDate(nowDate); TimeProvider.Current = testTimeProvider; //... //Assert.Equals(nowDate, actual); } }
Use Ambient Context properly or none. This pattern is dangerous and could cause hard to track bags. This could happen if you use value returned by Ambient Context for calculation or any conditional expression in the application
Dependency Injection antipatterns
Now we know how to inject our dependencies, but what should we avoid?
Control Freak
Control Freak – the problem is that client class creates it’s volatile dependencies by itself, using new operator. This approach is awkward because:
- We cannot change implementation of dependency.
- It is difficult to reuse because we must provide all required dependencies (sometimes it requires another modules, so we must deploy additional libraries).
- Parallel development is awkward because our code is tightly coupled with dependencies.
- It is difficult to test because we can’t use test doubles.
Constrained Construction
We are talking about Constrained Construction when constructor of dependency must have certain signature. This kind of problem usually appears when we create our own dependency injection framework.
We should remember that properly configured Dependency Injection Container can create every class, no matter how constructor’s signature looks like. Sometimes it requires creating wrappers, for classes which are factories or for classes which get collection of items, that implement dependency, in constructor. One of the biggest challenge of Object Composition is dragging all registration code to one place called Composition Root. I’m going to describe such situations in next article.
Bastard Injection
If our class uses implementation of abstraction from different module as default, then we talk about have Bastard Injection. In such a situation client module can’t live without module where implementation lives. This is awkward in deployment.
//ModuleC.dll interface Dependency { }; //ModuleA.dll class ImplementationOfDependency : Dependency { } //ModuleB.dll class Client { private readonly Dependency dependency; public Client() { //use implementation of dependency from different assembly this.dependency = new ImplementationOfDependency(); } }
In the code above modules ModuleA.dll and ModuleB.dll has been tightly coupled. We just can’t use ModuleA.dll without ModuleB.dll
Service Locator
Service Locator – it’s an object which returns implementation of abstraction for demand. Usually is created as a static factory, which and configured before first use.
Service Locator used to be consider as proper dependency injection pattern because:
- It allows late binding by configuration.
- It allows parallel development because we create software against interfaces not implementation, so we can change implementation whenever we want to.
- It allows to create Test Doubles, so testability isn’t a problem.
So why Service Locator is an antipattern ? There are some issues related to Service Locator:
- Modules should not require any dependencies to code which serves dependency.
- A code should not be aware if and what dependency injection container has been used. A client class should clearly communicate that it has a dependency.
Summary
After reading this article you should know what kind of dependencies you should inject, how to pass dependecies to your client classes and what you should to avoid. In this article I did not describe what dependency injection framework is. In next article I’m going to describe Object Composition and show you Unity Dependency Injection Framework in action.