This is part one of a three-part series on dependency injection, which is a software design pattern that allows the removal of hard-coded dependencies and makes it possible to change them, whether at run-time or compile-time. Today’s topic covers the general principles behind dependency injection and its architectural benefits. Parts two and three will cover dependency injection in OSGi and Spring respectively.
Low coupling and high cohesion between software components is one of the first major principles of object oriented analysis and design. A system composed of modular, focused components with loose dependencies on each other that can be easily reconfigured should be easily maintainable, adaptable to change and growth, and potentially provide code reuse. However, as good as these advantages sound, the principle alone does not offer a clear path toward implementing such a design.
Programming to an interface is the first step to achieving low coupling. If a program has two components A and B, and either has a direct reference to each other’s concrete class, they become tightly coupled by definition regardless of how cohesive they may be.
Two solutions are readily apparent: either A and B should not know about each other (with a middle-man facilitating communications); or they should only know about each other’s interfaces, allowing the concrete implementation of a component to be switched out without changing the other components that rely on it. This blog post will focus on the latter approach; the former approach (commonly employed in messaging systems) will be covered in a future post.
Programming to an interface alone is not enough to achieve low coupling. Consider the following question: What entity is responsible for instantiating all of a program’s concrete components and passing their references to other components that wish to use them? If component A only knows about the interface of component B, it cannot create a concrete instantiation of B without defeating the primary purpose of using the interface in the first place. One solution could be to use a highly configurable “executive” component that would be responsible for creating specified concrete instantiations of A and B, informing them of references to the other components they depend on without ever exposing their dependencies’ concrete classes, and notifying components when all of their dependencies have been met via a post-constructor “activation” or “initialization” method. If there were an easy way to use such an executive, developers could consolidate complicated component initialization code into a single source while strongly promoting low coupling.
Thankfully, there is a design pattern that realizes the need for an executive component: dependency Injection. Dependency injection allows a developer to specify a set of components to create, what interfaces those components publically expose, and how many instances of other implementations of specified interfaces they require. This blog series focuses on implementing this pattern within Java, but the same principles can apply to any object oriented programming language.
While dependency injection can technically be achieved in Java by creating a class that contains the application’s “main method” (or a method that is invoked shortly after startup) that instantiates the application’s major concrete components and passes their references to each other, this approach still results in one hard-coded component that is highly coupled to all other components.
Instead of creating applications from scratch, modern large scale Java application developers utilize application frameworks to provide dependency injection functionality and additional configurability benefits. Developers give control of their application’s “main method” up to the framework, allowing the framework to initialize and call their components when the application executes as instructed through external configuration or annotations.
This next two entries in this blog series cover two major Java frameworks commonly used in the industry today, Spring and OSGi, which each provide unique implementations of the dependency injection concept. Be sure to connect with us on LinkedIn and Twitter at @CrederaOpen for further updates. Please use the comments section below or email us at email@example.com for questions or further discussion.