Equinox IT Blog

Dependency Inversion vs Dependency Injection vs Inversion of Control vs Programming to Interfaces

Sometimes it's important to differentiate

In this post I’ll discuss a few Design Patterns in common use by software developers and solution architects, which I’ve found to regularly cause confusion. This post is primarily directed at practitioners with some intermediate knowledge of these patterns since the context would help, but it will hopefully also help those just coming into contact with these concerns and provide a useful foundation.

Introduction

One of the goals of the Design Pattern language is a shared vocabulary to speed up communicating complex concepts. Using the appropriate language means we don't need to elaborate on intent. For example, if a set of classes are labelled command pattern the intent behind them is very different to the same classes labelled strategy pattern - even though the class definitions are very similar.

When we talk about Dependency Inversion vs Dependency Injection etc., we should also use the most appropriate language to avoid setting up someone else's mental model incorrectly. 

What follows is what I believe are the generally accepted definitions, but I’ve gone into some detail to highlight some of the nuance behind the principles. This nuance is sometimes glossed over and that can derail efforts to set up a shared understanding. Grab a cup of coffee and....

Dependency Inversion Principle

Bob Martin's original description is

A. High level modules should not depend upon low level modules. Both should depend upon abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions.

We use abstractions as a technique to remove unnecessary detail and to get to the essential minimal concepts. We then use this reduced set to avoid focussing on detail. (This is important given our limited ability to keep many things in mind simultaneously, i.e. to help our cognitive load).

It doesn't say interfaces, that's an implementation detail. One can easily create an interface with lots of diverse methods at a fine detail level. We wouldn't have captured the essence of the abstraction. Here's an example of an interface that doesn't abstract or hide much. The domain isn’t important, but we are dealing with a ‘service’ class that transforms xml into some other representation:

ITransformationService - an example of an interface that doesn't abstract or hide much

This interface is useful in many ways, including the ability to mock out during testing and to change the implementation as needed without breaking the calling code. The level of policy abstraction is not particularly high though. With this design we can do many transformation service related things, but a higher level policy doesn't jump out. 

Martin Fowler gives a bit more detail in his view of the Dependency Inversion Principle

  • Abstractions should not depend on details
  • Code should depend on things that are at the same or higher level of abstraction
  • High level policy should not depend on low level details
  • Capture low-level dependencies in domain-relevant abstractions

The last bullet point is key. Another interface definition:

ITransformationProgressService - an example of an interface where policies, or domain relevant abstractions, are starting to emerge

In this example policies, or domain relevant abstractions, are starting to emerge. The abstractions are higher and closer to the domain.

If the client (i.e. the domain layer) declares this requirement and continues to rely on the abstraction, and we supply an implementation (i.e. the service layer) to the client, then we would have achieved a shift; the implementation relies on a policy owned by the component requiring external dependencies/services. Factor in namespace concerns, and you'll end up with an interface defined in a package where the higher level client is located.

Example:

We start with a typical service/consumer type of arrangement where we're programming to interfaces but with no dependency inversion. The IProgressService is defined in the 'services' technical package (similar situation could be illustrated with Data Access Objects or DAO's). The application or domain logic is in the Transformer class, in the model package.

-----------------------------------------------------------
package myapp.model.transformation;
-----------------------------------------------------------

public class Transformer{
    // Depending on lower level package 'myapp.services'
    // Coding to interfaces though
    private myapp.services.IProgressService progressService;
   
    // Dependency is injected in the constructor
    public Transformer(IProgressService transformationService){
        this.progressService=progressService;
    }
   
    // A domain method
    public void compliantTransform(Document doc){
        if (locked()) {
            throw new IllegalStateException("May not transform a " +
                      " locked document, see governance policy 5.44.2")
        }
        doc.lock();
        if (progressService.progressToTransforming(doc.getId())){
            
            doDomainStuff(doc);
            progressService.progressToComplete(doc.getId());           
        } else {
            throw new NoNotificationException("Transform my not " +
            "proceed without proper notification, see governance policy "             +"15.24.-17.78")
        }      
    }
}
public class Document {
    // Some document fields and behaviour
}
-----------------------------------------------------------
package myapp.services;
-----------------------------------------------------------
public interface IProgressService {
   
    void progressToTransforming(Document doc);
    void progressToComplete(Document doc);
}
 
// Depends on interface in own package
public class EmailSendingProgressService implements IProgressService {
   
    public void progressToTransforming(Document doc){
        // send email with failover
    }
    public void progressToComplete(Document doc){
        // send email with failover, include publishing house too
    }
}

Note that we depend on a domain class Document in the
progressToTransforming method parameter! This means the
myapp.services.IProgressService depends on
myapp.model.transformation.Document and
myapp.model.transformation.Transformer depends
myapp.services.IProgressService = Results in circular package entanglement and layering benefits are lost

The Inversion

Now we invert the dependency, with the Email sending service depending on the higher order policy defined in the domain package. The abstraction dictates what service layer must adhere to - the domain requirements. Here the dependency is inverted, where once it was model depending on the services layer, it's now the service depending on the progress abstraction defined in the model.

-----------------------------------------------------------
package myapp.model.transformation;
-----------------------------------------------------------

// Co-located with the Transformation Progress Service abstraction
public class Transformer{

    // still programming to interfaces  
    private IProgressService progressService;   

    // Dependency is injected
    public Transformer(IProgressService transformationService){

        this.progressService=progressService;
    }

    public void compliantTransform(Document doc){
        // omitted for brevity       
    }

}
// Moved from service layer into the model layer
public interface IProgressService {

    void progressToTransforming(Document doc);
    void progressToComplete(Document doc);

public class Document {
    // Some document fields and behaviour
}

-----------------------------------------------------------
package myapp.services;
-----------------------------------------------------------
// Depends on interface in 'higher' level package, i.e. depends on the package containing the component requiring external dependencies/services 

public class EmailSendingProgressService implements IProgressService
{   

    public void progressToTransforming(Document doc){
        // SMTP send email with failover
    }

    public void progressToComplete(Document doc){
        // SMTP send email with failover, include publishing house too
    }

}

Notice how the lower level EmailSendingProgressService depends on the myapp.model.transformation namespace for both the Document class and the progress service abstraction, no more circular dependencies.

Dependency Injection

The passing of a dependency or collaborator to another class, often via constructors or setters, but also through a parameter in the method that needs it. In the example above the Transform class is handed an IProgressService in its constructor. Rather than the Transform instantiating or looking up a suitable instance, the dependency is injected or provided.

(A factory or service locator could be injected, but that defeats the intent somewhat - we want to be explicit in terms of the needed collaborators, a service locator could confuse matters if it supplies lots of different types.)

Inversion of Control - Hollywood Principle

This is probably not what people think of when 'Inversion of Control' is mentioned alongside Dependency Injection, but the original phrase was coined in 1988 by Ralph E. Johnson & Brian Foote in Designing Reusable Classes .

The general philosophy is that control is handed over. For example in a plugin framework, you're expected to override some call back methods. You cannot be sure when exactly program flow will find its way to them, but you need to be ready for it. The Template Method Pattern (Gang of Four) is another example where the abstract superclass will invoke the overridden methods. Your code doesn't instruct the abstract class, it calls you, hence the Hollywood phrase 'Don't call us, we'll call you'.

Inversion of Control - IoC Containers

This is a specific style of inversion of control where containers control how collaborators are wired together. Instead of the application wiring collaborators together, IoC containers can do the heavy lifting provided you tell them how you want things wired, e.g. annotation, definition files or in code.

An IoC container facilitates the wiring of components and collaborators. They typically inject the collaborators into the instances.

Programming to Interfaces

This term came from the landmark Gang of Four book 'Design Patterns: Elements of Reusable Object-Oriented Software'. In the introduction Erich Gamma mentions it as a key principle for reusable object oriented design.

Of all the terms covered here, I think this is the most intuitive: decouple your classes from the implementation and code to abstractions. This gives you flexibility to change the implementation. The interface should encapsulate the design, not be an explosion of incohesive methods, it should distil the collaboration or vocabulary between objects. A good interface should make the interaction understandable. If you need to dive into an implementation, you haven't captured the essence.

Wrap Up

The patterns and idioms described here have nuances which are sometimes glossed over and not always explored in the requisite detail. In particular, the dependency inversion principle and its reliance on the concepts of abstractions and policy seems to be misunderstood on first reading (or scanning which we often need to do).

It is the understanding of this depth and the appropriate use of the labels that allows us to accelerate the communication of ideas around class and component architecture. I hope this post has highlighted some of the depth behind them.

References

Simon Havenith is a Senior Consultant based in Equinox IT's Wellington, New Zealand office.

The Agile Architect training course

Subscribe by email