Chapter 1. Dependency injection: whatâs all the hype?
This chapter covers:
- Seeing an object as a service
- Learning about building and assembling services
- Taking a tour of pre-existing solutions
- Investigating the Hollywood Principle
- Surveying available frameworks
âWe all agree that your theory is crazy, but is it crazy enough?â
Niels Bohr
So youâre an expert on dependency injection (DI); you know it and use it every day. Itâs like your morning commuteâyou sleepwalk through it, making all the right left turns (and the occasional wrong right turns before quickly correcting) until youâre comfortably sitting behind your desk at work. Or youâve heard of DI and Inversion of Control (IoC) and read the occasional article, in which case this is your first commute to work on a new job and youâre waiting at the station, with a strong suspicion you are about to get on the wrong train and an even stronger suspicion youâre on the wrong platform.
Or youâre somewhere in between; youâre feeling your way through the idea, not yet fully convinced about DI, planning out that morning commute and looking for the best route to work, MapQuesting it. Or you have your own home-brew setup that works just fine, thank you very much. Youâve no need of a DI technology: You bike to work, get a lot of exercise on the way, and are carbon efficient.
Stop! Take a good, long breath. Dependency injection is the art of making work come home to you.
1.1. Every solution needs a problem
Most software today is written to automate some real-world process, whether it be writing a letter, purchasing the new album from your favorite band, or placing an order to sell some stock. In object-oriented programming (OOP), these are objects and their interactions are methods.
Objects represent their real-world counterparts. An Airplane represents a 747 and a Car represents a Toyota; a PurchaseOrder represents you buying this book; and so on.
Of particular interest is the interaction between objects: An airplane flies, while a car can be driven and a book can be opened and read. This is where the value of the automation is realized and where it is valuable in simplifying our lives.
Take the familiar activity of writing an email; you compose the message using an email application (like Mozilla Thunderbird or Gmail) and you send it across the internet to one or more recipients, as in figure 1.1. This entire activity can be modeled as the interaction of various objects.
Figure 1.1. Email is composed locally, delivered across an internet relay, and received by an inbox.
This highlights an important precept in this book: the idea of an object acting as a service. In the example, email acts as a message composition service, internet relays are delivery agents, and my correspondentâs inbox is a receiving service.
1.1.1. Seeing objects as services
The process of emailing a correspondent can be reduced to the composing, delivering, and receiving of email by each responsible object, namely the Emailer, InternetRelay, and RecipientInbox. Each object is a client of the next.
Emailer uses the InternetRelay as a service to send email, and in turn, the InternetRelay uses the RecipientInbox as a service to deliver sent mail.
The act of composing an email can be reduced to more granular tasks:
- Writing the message
- Checking spelling
- Looking up a recipientâs address
And so on. Each is a fairly specialized task and is modeled as a specific service. For example, âwriting the messageâ falls into the domain of editing text, so choosing a TextEditor is appropriate. Modeling the TextEditor in this fashion has many advantages over extending Emailer to write text messages itself: We know exactly where to look if we want to find out what the logic looks like for editing text.
- Our Emailer is not cluttered with distracting code meant for text manipulation.
- We can reuse the TextEditor component in other scenarios (say, a calendar or note-taking application) without much additional coding.
- If someone else has written a general-purpose text-editing component, we can make use of it rather than writing one from scratch.
Similarly, âchecking spellingâ is done by a SpellChecker. If we wanted to check spelling in a different language, it would not be difficult to swap out the English SpellChecker in favor of a French one. Emailer itself would not need to worry about checking spellingâFrench, English, or otherwise.
So now weâve seen the value of decomposing our services into objects. This principle is important because it highlights the relationship between one object and others it uses to perform a service: An object depends on its services to perform a function.
In our example, the Emailer depends on a SpellChecker, a TextEditor, and an AddressBook. This relationship is called a dependency. In other words, Emailer is a client of its dependencies.
Composition also applies transitively; an object may depend on other objects that themselves have dependencies, and so on. In our case, SpellChecker may depend on a Parser to recognize words and a Dictionary of valid words. Parser and Dictionary may themselves depend on other objects.
This composite system of dependencies is commonly called an object graph. This object graph, though composed of many dependencies, i...