Practical Dependency Injection

1. Introduction

The first installment of this two-part series explains (at a high level) the concepts of dependency inversion (DIP), inversion of control (IoC), Dependency Injection (DI) and IoC/DI Container—as well as how they relate with each other. We’ll also illustrate some practical DI patterns using C#.

2. Why Dependency Injection (DI)?

a. S.O.L.I.D

Introduced by Robert C. Martin in his 2000 paper, Design Principles and Design Patterns, the S.O.L.I.D. principles of Object-Oriented Programming are five design principles that make software designs more understandable, flexible and maintainable. Though there’s a lot of literature out there on these principles, they’re essentially self-explanatory:

  • Single Responsibility Principle
    • A class should have one, and only one, reason to change.”
  • Open Closed Principle
    • Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”
  • Liskov Substitution Principle
    • Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”
  • Interface Segregation Principle
    • Clients should not be forced to depend upon interfaces that they do not use.”
  • Dependency Inversion Principle. (DIP)
    • High-level modules should not depend on low-level modules. Both should depend on abstractions (interfaces).
    • Abstractions should not depend upon details. Details should depend upon abstractions.

b. DIP

What’s achieved by having both high-level and low-level modules dependent on interfaces is the loose coupling between the components, so that changing an object that another component depends on should have no code change on the one that depends on it. It also achieves testability and replaceability since components in a loosely coupled system can be replaced with alternative implementations that provide the same services.

But even when the DIP is implemented correctly, the problem of dependency on the lower level class still persists. The interface insures the decoupling of the lower class, but not the instantiation. Along comes the Dependency Injection technique to remove the dependency on the lower level class by separating the usage from its creation. This way, DI ensures the loose coupling and follows the DIP and the Single Responsibility Principle.

c. DI

Dependency injection is realized with four roles:

  1. The service – object to be used
  2. The client – consumes the service
  3. An interface – used by the client and implemented by the service
  4. The injector – creates a service instance and injects it into the client

Three of these roles are already created if the DIP is followed: the 1. service and 2. client are the high and low-level classes that should depend on the 3. interface. The injector role may be implemented with creational patterns—such as factory—but it is also implemented by several available frameworks–DI containers–like Castle Windsor, Unity, Ninject and others. Those frameworks were also historically called IoC (Inversion of Control) containers, but since DI is just a subset of IoC, it’s more appropriate to call them DI containers. These will be covered in greater detail in the second installment of this blog series.

3. DI Patterns

In OO world, Dependency Injection may be implemented in several ways:

  • Constructor Injection – the most frequently used
  • When Constructor Injection isn’t applicable, two options are available:
    • Property Injection
    • Method Injection

The difference between these depends on where the dependency is being injected. Let’s consider a quick example that assumes a client class: Consumer and a service class: Provider with a method: Provide. In essence, want to inject Provider into Consumer.

Here’s the Provider class:

using System;
namespace DI
{
   public interface IProvider
   {
      void ProvideService();
   }
   public class Provider : IProvider
   {
      public void ProvideService()
      {
         Console.WriteLine("ProvideService implementation here");
      }
   }
}

Below is shown an example of constructor injection. This way, the Provider service is available in the whole Consumer object.

namespace DI
{
   public class Consumer
   {
      private readonly IProvider _provider;
      public Consumer(IProvider provider)
      {
         _provider = provider;
      }
      public void InitiateServiceMethod()
      {
         _provider.ProvideService();
      }
   }
}

As mentioned above, this is the most-used means of injection, the only drawback being that the entire dependency chain is instantiated at startup.

When the client wants to choose which service implementation to use among different providers, Property Injection comes into play. Here are the two providers:

using System;
namespace DI
{
   public class Provider1 : IProvider
   {
      public void ProvideService()
      {
         Console.WriteLine("Provider1 ProvideService implementation");
      }
   }
   public class Provider2 : IProvider
   {
      public void ProvideService()
      {
         Console.WriteLine("Provider2 ProvideService implementation");
      }
   }
}

At this point it’s up to client to decide which provider to use. Assuming Provider1 one is the default, the following approach will allow Provider2 to be injected on as-needed basis, since the property: Provider below depends on the interface: IProvider

namespace DI
{
   public class ConsumerWithPropertyInjection
   {
      public IProvider Provider { get; set; }
      public ConsumerWithPropertyInjection()
      {
         Provider = new Provider1();
      }
      public void InitiateProvideServiceMethod()
      {
         Provider.ProvideService();
      }
   }
}

When there’s no need for the dependency object to be available in every operation, the Method Injection comes in handy.

namespace DI
{
   public class ConsumerWithMethodInjection
   {
      public void InitiateProviderServiceMethod(IProvider provider)
      {
         provider.ProvideService();
      }
   }
}

This way, the dependency was passed as a parameter to the method: InitiateProviderServiceMethod and can be changed easily at every method call.

In all the above scenarios, loose coupling and testability are preserved.

Next steps

Now that we’ve seen what DI is and how it can be achieved manually, the second installment of this blog series will briefly cover the most popular DI frameworks, followed by a dive into Ninject. In the meantime, if you have any questions around Practical Dependency Injection, please don’t hesitate to reach out to us. We’d love to help you get started.

Share on

linkedin sharing button twitter sharing button

Ready to get started?

Enter your information to keep the conversation going.
Location image
4 Sentry Parkway East, Suite 300, Blue Bell PA, 19422

Email Image

info@anexinet.com

Phono Image610 239 8100


Location Image4 Sentry Parkway East, Suite 300, Blue Bell PA, 19422
Phono Image610 239 8100