Author: Brandon Pearman

The views expressed here are mine alone and do not reflect the view of my employer.

"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." - Robert Martin

Dependency Inversion Principle is about decoupling. A low coupled application is easy to maintain and test, since it removes the difficulty of having to replace coupled dependencies. analogy: solidering appliances to a wall socket is a bad idea rather have plugs allowing for appliances to easily switch out.

High-level modules should not depend on low-level modules. Both should depend on abstractions. eg High-level objects like a service class which performs business logic, should not directly depend on low-level objects, like those of the infrastructure which fetch data. Instead of service classes depending on repo classes, both should depend on an interface or an abstract class.

Example of High-level modules depending on low-level modules (Coupled classes)

                  public class CustomerService
                  {
                      private CustomerRepo _customerRepo;

                      public CustomerService(CustomerRepo customerRepo)
                      {
                        _customerRepo = customerRepo;
                      }

                      public void GetCustomer(int id)
                      {
                          _customerRepo.Get(id);
                      }
                  }
                    

CustomerService depends directly on CustomerRepo, they are tightly coupled. Replacing tightly coupled classes can become extremely difficult and error prone.

collapse
Depending on abstractions example(Decoupled)

                public class CustomerService
                {
                    private ICustomerRepo _customerRepo;

                    public CustomerService(ICustomerRepo customerRepo)
                    {
                        _customerRepo = customerRepo;
                    }

                    public void GetCustomer(int id)
                    {
                        _customerRepo.Get(id);
                    }
                }
                  

A concrete CustomerRepo needs to be injected into CustomerService. CustomerService and CustomerRepo both depend on the interface ICustomerRepo, they have been decoupled. CustomerService has no ties to CustomerRepo and visa versa, therefore you could delete or replace CustomerRepo without breaking anything.

collapse

Abstractions should not depend on details. Details should depend on abstractions. eg details of the concretion should not be known by abstraction otherwise all concretions(including those in the future) will require that detail.

Abstractions depending on details example

                public interface ICustomerRepository : IDisposable
                {
                    string GetById(Guid id);
                    string[] GetAll();
                }
                  

It's not obvious that ICustomerRepository concretions would need to clean up any resources. The interface therefore leaks implementation details and therefore violates the Dependency Inversion Principle. ie this interface forces the concrete class to implement IDisposable when it may not need it, the concrete class needs to decide that detail itself. This interface is now getting involved in details.

abstractions-depending-on-details-example
Details depending on abstractions example

                    public class CustomerRepository : ICustomerRepository, IDisposable
                    {
                        public string GetById(Guid id)
                        {
                            throw new NotImplementedException();
                        }

                        public string[] GetAll()
                        {
                            throw new NotImplementedException();
                        }

                        public void Dispose()
                        {
                            throw new NotImplementedException();
                        }
                    }
                      

This class needs to clean up resources and therefore implements IDisposable. Other concretions of this interface may not need to clean up resources, therefore the ICustomerRepository should not be concerned with that detail.

collapse

Inversion of Control

IOC is the idea of using some technique to invert control but it does not define which technique nor does it define the use of abstractions. eg service locator pattern allows for IOC but does not use dependency injection

Inversion of Control

Inversion of Control is the idea, that the caller of a method, has the information the method needs, to do its job. Instead of a class having direct control, over the code it runs, it gets given code to run.

IoC can been achieved with:

  1. Dependency Injection
  2. Service locator Pattern
  3. Contextualized lookup
  4. Template method design pattern
  5. Strategy design pattern

public IoCExample()
{
    IAnimalRepo animal = new RatRepo(); // This class instantiates its dependency
    animal.Get(); // It now has control over exactly what code will run (eg RatRepo's Get function)
}

public IoCExample(IAnimalRepo animal) // Another class instantiates the dependency required and injects it to this method
{
    animal.Get(); // this class just knows it has to run some code but another class has control over what its is
    // eg animal could be rat, cat, dog and the code run will be different depending on what the outside class decides
}
  

Inversion of Control is the general difference beween a library and a framework. The client is in control over a library, calling methods directly. In a framework, control has been inverted, so the framework is in control of the sequence of calls.

collapse

Dependency Inversion Principle

DIP defines the use of abstractions as a principle.

Dependency Inversion Principle

Dependency Injection

DI is the action of injecting dependencies. ie passing parameters into a constructor/method/property

Dependency Injection

Dependency Injection is a technique to help you achieve Inversion of Control by Instantiating dependencies outside of the classes that use them. Dependency Injection simply means passing arguments to a constructor, property or method;


                    public class SomeClass
                    {
                        // constructor injection
                        public SomeClass(DependencyObj obj) // constructor injection should be used 99% of the time
                        {
                        DependencyObj.DoSomething();
                        }

                        // property injection
                        public DependencyObj DependencyObj { get; set; } // Only use if the rest of the class is not dependant on this property (may cause Temporal coupling)

                        // method injection
                        public void DoSomething(DependencyObj obj)
                        {
                        DependencyObj.DoSomething();
                        }
                    }
                      

In the above example the DependencyObj is instantiated somewhere else and passed/injected into the method, like this:


    public void Main()
    {
    DependencyObj dependencyObj = new DependencyObj();
    _someClass.DoSomething(dependencyObj);
    }
  

Volatile dependencies should always be injected for easy testing and maintenance. Dependencies are Volatile if the class:

  • gets data from outside the app like a db or api
  • uses non-deterministic dependencies like DateTime or Random
collapse

DI/IoC Containers

DI Containers do auto dependecy injection.

DI/IoC Containers

Simply put: DI Containers do auto Dependency Injection. The DI container knows how to create the dependency and its dependencies so it can inject dependecies when they are required. Take a look at Mark Seemann's post on "When to use a DI Container" for further reading.

Some examples of DI Containers:

  • Autofac
  • Unity
  • Castle.Windsor
  • Grace
  • Ninject
  • Microsoft.Extensions.DependecyInjection

IOC container benchmark performance comparison

DI containers seem magical but the basis of how it works is quite simple. I wrote a simple custom DI container as an example to get a simple understanding of how they work.

DI container features

There are added features to some DI container packages. Selecting a package is about finding the right container with the features you need.

  • Interception: Interceptions create a pipline allowing code to be run before and/or after the called method. eg when your code calls a specific method which has an interception pipeline the DI container will run some Cross-cutting concern.
  • Late Binding / XML configuration: Late binding is where a configuration file manages the injection types.
  • Convention-based Configuration or Auto-registration: With Auto-registration the container, is only given the interface, and it discovers the concrete type within the assembly. eg IMyClass is given to the container and it finds MyClass
  • Named dependencies: Named dependencies allows, the application to inject a type based on its parameter name.

Pure DI

Pure DI is when you use DI principles and patterns, but not a DI Container. You can still incorporate the features from containers eg You can achieve interception with Pure DI by using the Decorator Pattern. Pure DI was originally called "poor mans DI" by Mark Seemann but he retracted that to call it "Pure DI" because "poor mans DI" made it sound inferior which it is not.

collapse

Check out these links for more info:

My design and architecture repo