SOLID Design Principles – Dependency Inversion Principle (DIP)

 

DIP states that the higher level modules should be coupled with the lower level modules with complete abstraction. Meaning

  • The high level modules should not depend on low level modules but both should depend on abstraction.
  • Abstraction should not depend on detail but detail should depend on abstraction.

Following DIP allows our code to be loosely coupled thus ensuring that the high level modules are dependent on abstraction rather than concrete implementation of low level modules. The Dependency Injection Principle is an implementation of this principle.

 DependencyInversionPrinciple

In the image above it is obvious that we would never solder the wire directly to the supply but a level of abstraction by multiple plugs by simply plugging in.

Dependencies

Before about the dependency inversion principle in detail let us first understand what dependencies are and what dependencies we add into our modules without even knowing.

We add many dependencies in our code during the course of development. If we are developing a .NET application we would most probably have a dependency on the .NET framework. That is not a major concern because the framework in unlikely to change during the course of our development. However our major concern should be the dependencies that we add into our application that might change like third party libraries. Another common dependency that we have in the code is the database dependency. So we should try to make sure these dependencies are not implicit but explicit so that we can replacement implementations for these easily. The following is the list of common dependencies that we might have in our applications.

  • Framework
  • Third Party Libraries
  • Database
  • File System
  • Email
  • Web Services
  • System Resources (Clock)
  • Configuration
  • The new keyword
  • Static methods
  • Thread.Sleep
  • Random

Mostly we add dependencies into the application when the higher level modules call the lower level modules and the high level modules instantiate the lower level modules as they need them.  For example the user interface logic could depend on the business logic and business logic might instantiate the Infrastructure classes, data access classes, etc. So the user interface logic could depend on the business logic and business logic could depend on the Infrastructure or data access logic.

 

Let us have a look at some code that violates DIP. Say we have a class order that exposes the checkout method and has 2 methods: ProcessOrder() and ProcessPayment(). We can see neither there are any explicit dependencies set for the order class nor are there any implicit dependencies in the Checkout method. We could also see that neither ProcessPayment nor ProcessOrder declare any dependencies.

public class Order

{
    public void Checkout()
    {
        ProcessPayment();
        ProcessOrder();
    }
}

 

When we go and have a look at the implementations of these methods we see that the ProcessPayment method has a dependency on the PaymentGateway to charge the credit card with the amount and the ProcessOrder method has a dependency on the InventorySystem to reserve the item in the inventory.

private void ProcessPayment()
    {
        // Instantiate the PaymentGateway
        PaymentGateway paymentGateway = new PaymentGateway();
        // Charge the card with amount
        paymentGateway.ChargeCreditCard();
    }

    private void ProcessOrder()
    {
        // Instantiate InventorySystem
        InventorySystem inventorySystem = new InventorySystem();
        // Reserve the item in inventory
        inventorySystem.ReserveInventory();
    }

So the problem starts when any of these systems (PaymentGateway or InventorySystem) is not available or changes. It issues with this types of implementation are

  • Tight coupling between classes (Order is tightly coupled with PayementGateway and InventorySystem)
  • Not easy to change implementation because to change the implementation we need to change the implementation of the Order class and it will violate the Open Close Principle.
  • Difficult to test

Dependency Injection

Dependency Injection is a technique that is used to allow calling code to inject dependencies a class needs when it is instantiated. It also goes by the name of the Hollywood principle (“Don’t call us, we’ll call you!”). So instead of creating an instance of the PaymentGateway we should be able to call some service to charge the credit card.

There are 3 popular techniques for Dependency injection

Constructor Injection

This is implemented by the use of strategy pattern wherein the dependencies are passed in the constructor of class. So the constructer specifies the dependencies it need to function completely and the calling code about the dependencies of the class.

 

class OrderConstructorInjection
    {
        private PaymentGateway _paymentGateway;
        private InventorySystem _inventorySystem;

        public OrderConstructorInjection(IPaymentGateway paymentGateway, IInventorySystem inventorySystem)
        {
            _paymentGateway = paymentGateway;
            _inventorySystem = inventorySystem;
        }

        public void Checkout()
        {
            ProcessPayment();
            ProcessOrder();
        }

        private void ProcessPayment()
        {
            // Charge the card with amount using dependency injected in constructor
            _paymentGateway.ChargeCreditCard();
        }

        private void ProcessOrder()
        {
            // Reserve the item in inventory using dependency injected in constructor
            _inventorySystem.ReserveInventory();
        }
    }

Pros

  • Class declares upfront what it needs to function properly
  • Class will always be in a valid state once constructed as it does not have any other dependency than the ones explicitly mentioned in the constructor.

Cons

  • Constructors might end up having too many parameters (design smell)
  • Some methods in the class might not use all the parameters passed in the constructor (design smell)
  • Some features like serialization might need a default constructor as well.

Property Injection

In this type of injection we pass the dependencies via properties. It is also known as setter injection.

public class OrderPropertyInjection
    {
        public IPaymentGateway _paymentGateway { get; set; }
        public IInventorySystem _inventorySystem { get; set; }

        public void Checkout()
        {
            ProcessPayment();
            ProcessOrder();
        }

        private void ProcessPayment()
        {
            // Charge the card with amount using class properties
            _paymentGateway.ChargeCreditCard();
        }

        private void ProcessOrder()
        {
            // Reserve the item in inventory using class properties
            _inventorySystem.ReserveInventory();
        }
    }

Pros

  • Flexible as the dependency can be changed at any time.

Cons

  • Objects may be in inconsistent state between construction and setting of dependency.

Parameter Injection

In this type of injection we pass the dependencies in the method directly as parameters.

public class OrderParameterInjection
    {
        public void Checkout(IPaymentGateway paymentGateway, IInventorySystem inventorySystem)
        {
            ProcessPayment(paymentGateway);
            ProcessOrder(inventorySystem);
        }

        private void ProcessPayment(IPaymentGateway paymentGateway)
        {
            // Charge the card with amount using dependencies passed as parameter
            paymentGateway.ChargeCreditCard();
        }

        private void ProcessOrder(IInventorySystem inventorySystem)
        {
            // Reserve the item in inventory using dependencies passed as parameter
            inventorySystem.ReserveInventory();
        }
    }

Pros

  • Gives us the granular level control on the dependencies that we need to inject
  • More flexible as we don’t need to modify anything in the rest of the class other than the method we are changing.

Cons

  • The method itself might end up with many parameters (design smell)
  • If we change the method signature then we might need to make changes at the places where this method is being used.

Where to instantiate objects

Now that we have made the implementation of the order class without any instantiations then where do we instantiate the dependencies. Below are few common places where we can instantiate the dependencies.

Default Constructor

We could have a default constructor that would instantiate the dependencies needed in the application. This approach is referred as poor man’s IoC

Main

We can instantiate the dependencies we need in the Main method of the application or startup routine of the application.

IoC Container

We could use an Inversion of Control (IoC) container.  IoC containers are responsible for object graph instantiation and the initiation happen when the application begins and IoC’s generally use code or configurations to figure out what is set up to use when an Interface is called for. We need to register the managed interfaces and implementations with the container and then the dependencies on Interfaces is resolved at application startup or runtime.

Few of the IoC containers available in .NET are

  • Microsoft Unity
  • StructureMap
  • Ninject
  • Windsor
  • Funq / Munq

 

 

Find the complete source code for this post at googledrive or skydrive.

Any questions comments and feedback are most welcome.

Leave a Reply

Your email address will not be published. Required fields are marked *