Share this
Dependency Inversion vs Dependency Injection vs Inversion of Control vs Programming to Interfaces
by Simon Havenith on 05 May 2016
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:
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:
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
- http://martinfowler.com/articles/dipInTheWild.html
- http://www.laputan.org/drc/drc.html
- http://martinfowler.com/bliki/InversionOfControl.html
- http://www.artima.com/lejava/articles/designprinciplesP.html
- Gang of Four book 'Design Patterns: Elements of Reusable Object-Oriented Software'
- http://aspiringcraftsman.com/2008/12/28/examining-dependency-inversion/
- http://www.objectmentor.com/resources/articles/dip.pdf
Simon Havenith is a Senior Consultant based in Equinox IT's Wellington, New Zealand office.
Share this
- Agile Development (153)
- Software Development (126)
- Agile (76)
- Scrum (66)
- Application Lifecycle Management (50)
- Capability Development (47)
- Business Analysis (46)
- DevOps (43)
- IT Professional (42)
- Equinox IT News (41)
- Agile Transformation (38)
- IT Consulting (38)
- Knowledge Sharing (36)
- Lean Software Development (35)
- Requirements (35)
- Strategic Planning (35)
- Solution Architecture (34)
- Digital Disruption (32)
- IT Project (31)
- International Leaders (31)
- Digital Transformation (26)
- Project Management (26)
- Cloud (25)
- Azure DevOps (23)
- Coaching (23)
- IT Governance (23)
- System Performance (23)
- Change Management (20)
- Innovation (20)
- MIT Sloan CISR (15)
- Client Briefing Events (13)
- Architecture (12)
- Working from Home (12)
- IT Services (10)
- Data Visualisation (9)
- Kanban (9)
- People (9)
- Business Architecture (8)
- Communities of Practice (8)
- Continuous Integration (7)
- Business Case (4)
- Enterprise Analysis (4)
- Angular UIs (3)
- Business Rules (3)
- GitHub (3)
- Java Development (3)
- Lean Startup (3)
- Satir Change Model (3)
- API (2)
- Automation (2)
- Scaling (2)
- Security (2)
- Toggles (2)
- .Net Core (1)
- AI (1)
- Diversity (1)
- Testing (1)
- ✨ (1)
- August 2024 (1)
- February 2024 (3)
- January 2024 (1)
- September 2023 (2)
- July 2023 (3)
- August 2022 (4)
- August 2021 (1)
- July 2021 (1)
- June 2021 (1)
- May 2021 (1)
- March 2021 (1)
- February 2021 (2)
- November 2020 (2)
- September 2020 (1)
- July 2020 (1)
- June 2020 (3)
- May 2020 (3)
- April 2020 (2)
- March 2020 (8)
- February 2020 (1)
- November 2019 (1)
- August 2019 (1)
- July 2019 (2)
- June 2019 (2)
- April 2019 (3)
- March 2019 (2)
- February 2019 (1)
- December 2018 (3)
- November 2018 (3)
- October 2018 (3)
- September 2018 (1)
- August 2018 (4)
- July 2018 (5)
- June 2018 (1)
- May 2018 (1)
- April 2018 (5)
- March 2018 (3)
- February 2018 (2)
- January 2018 (2)
- December 2017 (2)
- November 2017 (3)
- October 2017 (4)
- September 2017 (5)
- August 2017 (3)
- July 2017 (3)
- June 2017 (1)
- May 2017 (1)
- March 2017 (1)
- February 2017 (3)
- January 2017 (1)
- November 2016 (1)
- October 2016 (6)
- September 2016 (1)
- August 2016 (5)
- July 2016 (3)
- June 2016 (4)
- May 2016 (7)
- April 2016 (13)
- March 2016 (8)
- February 2016 (8)
- January 2016 (7)
- December 2015 (9)
- November 2015 (12)
- October 2015 (4)
- September 2015 (2)
- August 2015 (3)
- July 2015 (8)
- June 2015 (7)
- April 2015 (2)
- March 2015 (3)
- February 2015 (2)
- December 2014 (4)
- September 2014 (2)
- July 2014 (1)
- June 2014 (2)
- May 2014 (9)
- April 2014 (1)
- March 2014 (2)
- February 2014 (2)
- December 2013 (1)
- November 2013 (2)
- October 2013 (3)
- September 2013 (2)
- August 2013 (6)
- July 2013 (2)
- June 2013 (1)
- May 2013 (4)
- April 2013 (5)
- March 2013 (2)
- February 2013 (2)
- January 2013 (2)
- December 2012 (1)
- November 2012 (1)
- October 2012 (2)
- September 2012 (3)
- August 2012 (3)
- July 2012 (3)
- June 2012 (1)
- May 2012 (1)
- April 2012 (1)
- February 2012 (1)
- December 2011 (4)
- November 2011 (2)
- October 2011 (2)
- September 2011 (4)
- August 2011 (2)
- July 2011 (3)
- June 2011 (4)
- May 2011 (2)
- April 2011 (2)
- March 2011 (3)
- February 2011 (1)
- January 2011 (4)
- December 2010 (2)
- November 2010 (3)
- October 2010 (1)
- September 2010 (1)
- May 2010 (1)
- February 2010 (1)
- July 2009 (1)
- April 2009 (1)
- October 2008 (1)