Here I’m going to explain what I like to call “the coffee machine metaphor“. It is basically a real world story that quickly made me think of some common practices in programming. I’m going to explain the story and then we’ll visit the programming side of this. So, here we go!
In the office
at the company I work for, our coffee machine was a capsule coffee machine in which the user was responsible to add the correct capsule, put their mug/cup and, eventually, empty the used capsules tray. The machine didn’t have a signal to let the user know that the tray was getting full, so if the tray wasn’t checked, the person’s capsule could get stuck inside. So the person wouldn’t get their coffee, the machine began acting up, and productivity in the office was affected due to a lack of caffeine and lots of frustration.
Then one day they brought a brand new coffee machine into our office. It was bigger and better, and made coffee with a single press of a button. We, the employees, got really excited about it like kids with a new lollipop.
This new machine made our lives simpler since we only needed to press one button to get our desired coffee. From the user’s point of view, that can be a really big improvement. No more handling coffee capsules, no more worrying about the used capsules tray. Just press a button and enjoy your coffee.
This new machine also had a lot of plastic cups inside and it automatically used one of them when you ordered a coffee. A guy from the coffee company would come every other week in order to refill the cups and the coffee supply so we wouldn’t find ourselves without coffee. Everything was great!
Until you want something a little different.
Some coworkers and I had a custom mug made with a “Keep Calm and…” sentence we liked and I used to have my coffee in it with the old machine. Actually, I used to have a double coffee in my mug every morning. Before this new coffee machine, I accidentally dropped and shattered my beloved mug. So when the new coffee machine arrived, I was still missing my mug. I was missing my double coffee every morning. But if I had had it, I wouldn’t have been able to do my special coffee in my mug because the new machine used its cups automatically.
Dependency Injection for dummies
Even if I still had my mug, I would still miss my double coffee in my mug every morning. That, or ordering 2 coffees using 2 plastic cups which is a waste. In my opinion, a machine like that can work well in some places, like a hospital or a library (it actually had the hole to insert coins, but we didn’t have to pay fortunately), but not in a place where more than 30 people are there every day 9 to 5 Monday through Friday. This coffee machine was clearly not designed with these needs in mind, actually it was designed to do the opposite, to force you (in the name of simplicity) to use a resource you may not need nor want.
As a daily user of this machine, I want to be able to choose where my coffee is poured because:
- I like my mug
- I don’t want to waste plastic cups everyday
- maybe I want to reuse another plastic cup
- I don’t want the machine to decide for me where to put my coffee
From this point of view, it seems clear to me that I should be able to inject my mug object in the process so I can have the coffee done directly wherever I want it.
Now let’s think about code. The coffee machine represents now the class of the object you are working with. It can still sound great to leave it the responsibility to choose explicitly which resources it uses and how. It may be the most direct and fastest approach to implement and it can even sound like the “simplest” thing to do, but it’s not. It can lead to some serious problems. Depending on the nature of what the cup represents it can be a really bad choice to let the coffee machine decide.
Basically, I like to ask myself a question: what is the responsibility of this class I’m working on? The coffee machine’s responsibility is NOT to make coffee in a plastic cup. Its real responsibility is to make coffee. That’s it. Nothing else.
It could have an output where the coffee would be transferred to something else that is ready to receive it. Or, in order to make the machine work, it may need to have the coffee container provided. Note I haven’t said cup or mug. I said a coffee container, or even more, it could be a liquid container, because maybe someone wants to have their coffee in a wine glass. Anyways, the machine could be defined so it would work only when a container has been provided, if it has been injected.
There are different advantages of doing it this way. The most important ones are modularity and testability. The first one brings flexibility to your code. Making your code with less dependencies allows you to reuse your classses. The latter one, testability, allows your code to be unit tested without dealing with dependencies that should be independent. You want to test the coffee is done in the machine and when doing so you don’t really need a real liquid container.
We have changed from “making a coffee directly into a plastic cup” to “making a coffee into a provided container”.
Obviously all of this takes place at class level. Classes should be designed that way, making them as testable as possible and with the least amount of dependencies. Each class should perform a single part of a functionality. Less responsibilities in a class brings less side effects and less unexpected bugs. This probably takes us to create more classes in order to give the entire feature, but it’s completely acceptable because this modularity improves the project maintainability.