Lessons From 'Dependency Injection Principles, Practices, and Patterns'
Source: Dev.to
Introduction
My first job as a software developer was writing C# for a software development agency that worked very closely with Microsoft. As a junior engineer, I had a basic notion of object‑oriented programming and SOLID principles, but I was puzzled every time I opened a project that used a .NET dependency injection container. Why are we writing an interface for everything? Why aren’t we instantiating instances inside of classes? Why are we passing all instances through the constructor? It all seemed very bureaucratic. I didn’t understand why we were following these strict rules that added so much code, when we could keep the code lean and simple by getting rid of those boiler‑plate interfaces and instantiating the instances of a class where they were going to be used.
Fast‑forward a couple of years, while working for another company, I decided to pick up Dependency Injection Principles, Practices, and Patterns. I remember the book showing up in the background of a YouTuber’s video. At the time, I thought that reading it could help me introduce more structure and order to our projects. After reading the book, I can say it answered all of my questions and more. It has helped me have a much better understanding of object‑oriented programming and software design as a whole.
The authors, Steven van Deursen and Mark Seemann, do a great job of explaining central concepts to dependency injection such as decoupling, cohesion, and how they relate to the SOLID principles. They provide examples of the dependency injection pattern, common anti‑patterns, and code smells, all of which illustrate the main advantages of dependency injection. The book is very well written, and I highly recommend it to anyone working with object‑oriented programming.
Below are three ideas from the book that have had the biggest impact on how I approach software development. This is not a complete summary; to understand dependency injection fully, I encourage you to read the book.
Low Coupling
The book introduces the concept of coupling with an analogy to a hotel’s hair dryer. Many hotel hair dryers are wired directly into the socket. Like highly coupled code, it is a pain to fix or change. When the hair dryer breaks, an electrician must switch off the electricity in the room and de‑solder the cabling before replacing the dryer. With highly coupled code, you often have to change other modules before you can replace the module that needs fixing or refactoring.
In contrast, a hair dryer with a plug is easier to replace; you simply unplug it from the wall socket. Loosely coupled software behaves the same—it is easier to replace, remove, and maintain.
Maintainability is a crucial aspect of software development. A solution that is not maintainable has a very high cost to change, which means fewer feature releases for the business. Loosely coupled code improves a project’s maintainability, enabling faster development of new features.
High Cohesion
The authors discuss the SOLID principles, emphasizing the Single Responsibility Principle (SRP):
The Single Responsibility Principle states that each class should have only one reason to change.
In relation to SRP, the authors define cohesion as the functional relatedness of the elements of a class or module. Lower relatedness means lower cohesion, which increases the likelihood that a class violates SRP.
When aiming to write loosely coupled code, SRP becomes an invaluable guideline. Loose coupling should occur between modules that have a high level of cohesion; otherwise, the software becomes difficult to understand and maintainability suffers.
Volatile and Stable Dependencies
Although loosely coupled code contributes to a more maintainable code base, it also has a cost: more seams in your code.
Everywhere you program against an abstraction instead of a concrete type, you introduce a seam. A seam is a place where an application is assembled from its constituent parts, similar to how a piece of clothing is sewn together.
Because of this cost, it is important to introduce seams only where they are necessary, such as for volatile dependencies. A volatile dependency, as opposed to a stable one, meets one of the following criteria:
- It runs in a different environment (e.g., a different machine or process). An example would be a database.
- It does not exist yet or is under development.
- It contains nondeterministic behavior.
- New versions may contain breaking changes.
Conclusion
There are many other concepts I learned from this book that I haven’t covered here. It is an easy read thanks to its structure, explanations, and analogies. I recommend everyone buy it, read it at least once, and keep it on your bookshelf for whenever a question pops up.