Dependency inversion principle
In object-oriented programming, the dependency inversion principle refers to a specific form of decoupling software modules. When following this principle, the conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are reversed, thus rendering high-level modules independent of the low-level module implementation details. The principle states:
- A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
- B. Abstractions should not depend on details. Details should depend on abstractions.
The principle inverts the way some people may think about object-oriented design, dictating that both high- and low-level objects must depend on the same abstraction.
Traditional layers pattern
In conventional application architecture, lower-level components are designed to be consumed by higher-level components which enable increasingly complex systems to be built. In this composition, higher-level components depend directly upon lower-level components to achieve some task. This dependency upon lower-level components limits the reuse opportunities of the higher-level components.
The goal of the dependency inversion principle is to avoid this highly coupled distribution with the mediation of an abstract layer, and to increase the re-usability of higher/policy layers.
With the addition of an abstract layer, both high- and lower-level layers avoid the traditional dependencies from top to bottom. Nevertheless, the ″inversion″ concept does not mean that lower-level layers depend on higher-level layers. Both layers should depend on abstractions that draw the behavior needed by higher-level layers.
In a direct application of dependency inversion, the abstracts are owned by the upper/policy layers. This architecture groups the higher/policy components and the abstracts that define lower services together in the same package. The lower-level layers are created by inheritance/implementation of these abstract classes or interfaces.
The inversion of the dependencies and ownership encourages the re-usability of the higher/policy layers. Upper layers could use other implementations of the lower services. When the lower-level layer components are closed or when the application requires the reuse of existing services, it is common that an Adapter mediates between the services and the abstractions.
The presence of abstractions to accomplish DIP have other design implications in an object-oriented program:
- All member variables in a class must be interfaces or abstracts.
- All concrete class packages must connect only through interface or abstract class packages.
- No class should derive from a concrete class.
- No method should override an implemented method.
- All variable instantiation requires the implementation of a creational pattern such as the factory method or the factory pattern, or the use of a dependency-injection framework.
Two common implementations of DIP use similar logical architecture, with different implications.
A direct implementation packages the policy classes with service abstracts classes in one library. In this implementation high-level components and low-level components are distributed into separate packages/libraries, where the interfaces defining the behavior/services required by the high-level component are owned by, and exist within the high-level component's library. The implementation of the high-level component's interface by the low level component requires that the low-level component package depend upon the high-level component for compilation, thus inverting the conventional dependency relationship.
Figures 1 and 2 illustrate code with the same functionality, however in Figure 2, an interface has been used to invert the dependency. The direction of dependency can be chosen to maximize policy code reuse, and eliminate cyclic dependencies.
In this version of DIP, the lower layer component's dependency on the interfaces/abstracts in the higher-level layers makes re-utilization of the lower layer components difficult. This implementation instead ″inverts″ the traditional dependency from top-to-bottom to the opposite from bottom-to-top.
A more flexible solution extracts the abstract components into an independent set of packages/libraries:
The separation of all layers into their own package encourages re-utilization of any layer, providing robustness and mobility.
A more concrete example follows:
UI and ApplicationLayer packages contains mainly concrete classes. Controllers contains abstracts/interface types. UI has an instance of ICustomerHandler. All packages are physically separated. In the ApplicationLayer there is a concrete implementation that Page class will use. Instances of this interface are created dynamically by a Factory (possibly in the same Controllers package). The concrete types, Page and CustomerHandler, don't depend on each other; both depend on ICustomerHandler.
The direct effect is that the UI doesn't need to reference the ApplicationLayer or any concrete package that implements the ICustomerHandler. The concrete class will be loaded using reflection. At any moment the concrete implementation could be replaced by another concrete implementation without changing the UI class. Another interesting possibility is that the Page class implements an interface IPageViewer that could be passed as an argument to ICustHandler methods. Then the concrete implementation could communicate with UI without a concrete dependency. Again, both are linked by interfaces.
Applying the dependency inversion principle can also be seen as an example of the adapter pattern, i.e. the high-level class defines its own adapter interface which is the abstraction that the other high-level classes depend on. The adaptee implementation also depends on the adapter interface abstraction (of course, since it implements its interface) while it can be implemented by using code from within its own low-level module. The high-level has no dependency on the low-level module since it only uses the low-level indirectly through the adapter interface by invoking polymorphic methods to the interface which are implemented by the adaptee and its low-level module.
Various patterns such as Plugin, Service Locator, or Dependency Injection are employed to facilitate the run-time provisioning of the chosen low-level component implementation to the high-level component.
The dependency inversion principle was postulated by Robert C. Martin and described in several publications including the paper Object Oriented Design Quality Metrics: an analysis of dependencies, an article appearing in the C++ Report in May 1996 entitled The Dependency Inversion Principle, and the books Agile Software Development, Principles, Patterns, and Practices, and Agile Principles, Patterns, and Practices in C#.
- Adapter pattern
- Dependency Injection
- Design by contract
- Inversion of Control
- Plug-in (computing)
- Service locator pattern
- SOLID – the "D" in "SOLID" stands for the dependency inversion principle
- Martin, Robert C. (2003). Agile Software Development, Principles, Patterns, and Practices. Prentice Hall. pp. 127–131. ISBN 978-0135974445.
- Freeman, Eric; Freeman, Elisabeth; Kathy, Sierra; Bert, Bates (2004). Hendrickson, Mike; Loukides, Mike, eds. "Head First Design Patterns" (paperback). 1. O'REILLY. ISBN 978-0-596-00712-6. Retrieved 2012-06-21.
- Martin, Robert C. (October 1994). "Object Oriented Design Quality Metrics: An analysis of dependencies" (PDF). Retrieved 2016-10-15.
- Martin, Robert C. (May 1996). "The Dependency Inversion Principle" (PDF). C++ Report. Archived from the original (PDF) on 2011-07-14.
- Object Oriented Design Quality Metrics: an analysis of dependencies Robert C. Martin, C++ Report, Sept/Oct 1995
- The Dependency Inversion Principle, Robert C. Martin, C++ Report, May 1996
- Examining the Dependency Inversion Principle, Derek Greer
- DIP in the Wild, Brett L. Schuchert, May 2013
- IoC Container for Unity3D – part 2