Introduction to Dependency Injection

Introduction to Dependency Injection

data: 14 maja, 2014
czas czytania: 9 min
autor: Rafał Dąbrowa
Sooner or later all of us will hear acronym SOLID. Some friend of us will whisper us that our code should be SOLID.
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

DependencyInjectionMindMap

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.

Newsletter IT leaks

Dzielimy się inspiracjami i nowinkami z branży IT. Szanujemy Twój czas - obiecujemy nie spamować i wysyłać wiadomości raz na dwa miesiące.

Subscribe to our newsletter

Administratorem Twoich danych osobowych jest Future Processing S.A. z siedzibą w Gliwicach. Twoje dane będziemy przetwarzać w celu przesyłania cyklicznego newslettera dot. branży IT. W każdej chwili możesz się wypisać lub edytować swoje dane. Więcej informacji znajdziesz w naszej polityce prywatności.