What is the dependency inversion principle and why is it important?

Viewed 86544

What is the dependency inversion principle and why is it important?

16 Answers

What Is It?

The books Agile Software Development, Principles, Patterns, and Practices and Agile Principles, Patterns, and Practices in C# are the best resources for fully understanding the original goals and motivations behind the Dependency Inversion Principle. The article "The Dependency Inversion Principle" is also a good resource, but due to the fact that it is a condensed version of a draft which eventually made its way into the previously mentioned books, it leaves out some important discussion on the concept of a package and interface ownership which are key to distinguishing this principle from the more general advise to "program to an interface, not an implementation" found within the book Design Patterns (Gamma, et. al).

To provide a summary, the Dependency Inversion Principle is primarily about reversing the conventional direction of dependencies from "higher level" components to "lower level" components such that "lower level" components are dependent upon the interfaces owned by the "higher level" components. (Note: "higher level" component here refers to the component requiring external dependencies/services, not necessarily its conceptual position within a layered architecture.) In doing so, coupling isn't reduced so much as it is shifted from components that are theoretically less valuable to components which are theoretically more valuable.

This is achieved by designing components whose external dependencies are expressed in terms of an interface for which an implementation must be provided by the consumer of the component. In other words, the defined interfaces express what is needed by the component, not how you use the component (e.g. "INeedSomething", not "IDoSomething").

What the Dependency Inversion Principle does not refer to is the simple practice of abstracting dependencies through the use of interfaces (e.g. MyService → [ILogger ⇐ Logger]). While this decouples a component from the specific implementation detail of the dependency, it does not invert the relationship between the consumer and dependency (e.g. [MyService → IMyServiceLogger] ⇐ Logger.

Why Is It Important?

The importance of the Dependency Inversion Principle can be distilled down to a singular goal of being able to reuse software components which rely upon external dependencies for a portion of their functionality (logging, validation, etc.)

Within this general goal of reuse, we can delineate two sub-types of reuse:

  1. Using a software component within multiple applications with sub-dependency implementations (e.g. You've developed a DI container and want to provide logging, but don't want to couple your container to a specific logger such that everyone that uses your container has to also use your chosen logging library).

  2. Using software components within an evolving context (e.g. You've developed business-logic components which remain the same across multiple versions of an application where the implementation details are evolving).

With the first case of reusing components across multiple applications, such as with an infrastructure library, the goal is to provide a core infrastructure need to your consumers without coupling your consumers to sub-dependencies of your own library since coupling to such dependencies requires your consumers to require the same dependencies as well. This can be problematic when consumers of your library choose to use a different library for the same infrastructure needs (e.g. NLog vs. log4net), or if they choose to use a later version of the required library which isn't backward compatible with the version required by your library.

With the second case of reusing business-logic components (i.e. "higher-level components"), the goal is to isolate the core domain implementation of your application from the changing needs of your implementation details (i.e. changing/upgrading persistence libraries, messaging libraries, encryption strategies, etc.). Ideally, changing the implementation details of an application shouldn't break the components encapsulating the application's business logic.

Note: Some may object to describing this second case as actual reuse, reasoning that components such as business-logic components used within a single evolving application represents only a single use. The idea here, however, is that each change to the application's implementation details renders a new context and therefore a different use case, though the ultimate goals could be distinguished as isolation vs. portability.

While following the Dependency Inversion Principle in this second case can offer some benefit, it should be noted that its value as applied to modern languages such as Java and C# is much reduced, perhaps to the point of being irrelevant. As discussed earlier, the DIP involves separating implementation details into separate packages completely. In the case of an evolving application, however, simply utilizing interfaces defined in terms of the business domain will guard against needing to modify higher-level components due to changing needs of implementation detail components, even if the implementation details ultimately reside within the same package. This portion of the principle reflects aspects that were pertinent to the language in view when the principle was codified (i.e. C++) which aren't relevant to newer languages. That said, the importance of the Dependency Inversion Principle primarily lies with the development of reusable software components/libraries.

A longer discussion of this principle as it relates to the simple use of interfaces, Dependency Injection, and the Separated Interface pattern can be found here. Additionally, a discussion of how the principle relates to dynamically-typed languages such as JavaScript can be found here.

Check this document out: The Dependency Inversion Principle.

It basically says:

  • High level modules should not depend upon low-level modules. Both should depend upon abstractions.
  • Abstractions should never depend upon details. Details should depend upon abstractions.

As to why it is important, in short: changes are risky, and by depending on a concept instead of on an implementation, you reduce the need for change at call sites.

Effectively, the DIP reduces coupling between different pieces of code. The idea is that although there are many ways of implementing, say, a logging facility, the way you would use it should be relatively stable in time. If you can extract an interface that represents the concept of logging, this interface should be much more stable in time than its implementation, and call sites should be much less affected by changes you could make while maintaining or extending that logging mechanism.

By also making the implementation depend on an interface, you get the possibility to choose at run-time which implementation is better suited for your particular environment. Depending on the cases, this may be interesting too.

Good answers and good examples are already given by others here.

The reason DIP is important is because it ensures the OO-principle "loosely coupled design".

The objects in your software should NOT get into a hierarchy where some objects are the top-level ones, dependent on low-level objects. Changes in low-level objects will then ripple-through to your top-level objects which makes the software very fragile for change.

You want your 'top-level' objects to be very stable and not fragile for change, therefore you need to invert the dependencies.

Inversion of control (IoC) is a design pattern where an object gets handed its dependency by an outside framework, rather than asking a framework for its dependency.

Pseudocode example using traditional lookup:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

Similar code using IoC:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

The benefits of IoC are:

  • You have no dependency on a central framework, so this can be changed if desired.
  • Since objects are created by injection, preferably using interfaces, it's easy to create unit tests that replace dependencies with mock versions.
  • Decoupling off code.

Dependency Inversion Principle(DIP)

It is a part of SOLID[About] which is a part of OOD and was introduced by Uncle Bob. It is about loose coupling between classes(layers...). Class should not be depended on concrete realization, class should be depended on abstraction/interface

Problem:

//A -> B
class A {
  B b

  func foo() {
     b = B();
  }
}

Solution:

//A -> IB <|- B
//client[A -> IB] <|- B is the Inversion 
class A {
  IB ib // An abstraction between High level module A and low level module B

  func foo() {
     ib = B()
  }
}

Now A is not depended on B(one to one), now A is depended on interface IB which is implemented by B, it means that A depends on multiple realization of IB(one to many)

[DIP vs DI vs IoC]

The point of dependency inversion is to make reusable software.

The idea is that instead of two pieces of code relying on each other, they rely on some abstracted interface. Then you can reuse either piece without the other.

The way this is most commonly achieved is through an inversion of control (IoC) container like Spring in Java. In this model, properties of objects are set up through an XML configuration instead of the objects going out and finding their dependency.

Imagine this pseudocode...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClass directly depends on both the Service class and the ServiceLocator class. It needs both of those if you want to use it in another application. Now imagine this...

public class MyClass
{
  public IService myService;
}

Now, MyClass relies on a single interface, the IService interface. We'd let the IoC container actually set the value of that variable.

So now, MyClass can easily be reused in other projects, without bringing the dependency of those other two classes along with it.

Even better, you don't have to drag the dependencies of MyService, and the dependencies of those dependencies, and the... well, you get the idea.

If we can take it as a given that a "high level" employee at a corporation is paid for the execution of their plans, and that these plans are delivered by the aggregate execution of many "low level" employee's plans, then we could say it is generally a terrible plan if the high level employee's plan description in any way is coupled to the specific plan of any lower level employee.

If a high level executive has a plan to "improve delivery time", and indicates that an employee in the shipping line must have coffee and do stretches each morning, then that plan is highly coupled and has low cohesion. But if the plan makes no mention of any specific employee, and in fact simply requires "an entity that can perform work is prepared to work", then the plan is loosely coupled and more cohesive: the plans do not overlap and can easily be substituted. Contractors, or robots, can easily replace the employees and the high level's plan remains unchanged.

"High level" in the dependency inversion principle means "more important".

I can see good explanation has been given in above answers. However i wants to provide some easy explanation with simple example.

Dependency Inversion Principle allows the programmer to remove the hardcoded dependencies so that the application becomes loosely coupled and extendable.

How to achieve this : through abstraction

Without dependency inversion:

 class Student {
    private Address address;

    public Student() {
        this.address = new Address();
    }
}
class Address{
    private String perminentAddress;
    private String currentAdrress;

    public Address() {
    }
} 

In above code snippet, address object is hard-coded. Instead if we can use dependency inversion and inject the address object by passing through constructor or setter method. Let's see.

With dependency inversion:

class Student{
    private Address address;

    public Student(Address address) {
        this.address = address;
    }
    //or
    public void setAddress(Address address) {
        this.address = address;
    }
}

Dependency Inversion Principle (DIP) says that

i) High level modules should not depend upon low-level modules. Both should depend upon abstractions.

ii) Abstractions should never depend upon details. Details should depend upon abstractions.

Example:

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

Note: Class should depend on abstractions like interface or abstract classes, not specific details (implementation of interface).

Adding to the flurry of generally good answers, I'd like to add a tiny sample of my own to demonstrate good vs. bad practice. And yes, I'm not one to throw stones!

Say, you want a little program to convert a string into base64 format via console I/O. Here's the naive approach:

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-level I/O functionality.
         * Hence, you'll have to fiddle with BadEncoder whenever you want to change
         * the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
         * problems with I/O shouldn't break the encoder!
         */
        BadEncoder.Run();            
    }
}

public static class BadEncoder
{
    public static void Run()
    {
        Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
    }
}    

The DIP basically says that high-level components shouldn't be dependent on low-level implementation, where "level" is the distance from I/O according to Robert C. Martin ("Clean Architecture"). But how do you get out of this predicament? Simply by making the central Encoder dependent only on interfaces without bothering how those are implemented:

class Program
{
    static void Main(string[] args)
    {           
        /* Demo of the Dependency Inversion Principle (= "High-level functionality
         * should not depend upon low-level implementations"): 
         * You can easily implement new I/O methods like
         * ConsoleReader, ConsoleWriter without ever touching the high-level
         * Encoder class!!!
         */            
        GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter());        }
}

public static class GoodEncoder
{
    public static void Run(IReadable input, IWriteable output)
    {
        output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
    }
}

public interface IReadable
{
    string ReadInput();
}

public interface IWriteable
{
    void WriteOutput(string txt);
}

public class ConsoleReader : IReadable
{
    public string ReadInput()
    {
        return Console.ReadLine();
    }
}

public class ConsoleWriter : IWriteable
{
    public void WriteOutput(string txt)
    {
        Console.WriteLine(txt);
    }
}

Note that you don't need to touch GoodEncoder in order to change the I/O mode — that class is happy with the I/O interfaces it knows; any low-level implementation of IReadable and IWriteable won't ever bother it.

Related