The dependency inversion principle states:
- “High level modules should not depend upon low level modules. Both should depend upon abstractions.”
- “Abstractions should not depend upon details. Details should depend upon abstractions.”
See Principles of Object Oriented Class Design by Robert C. Martin (2000) for further details.
An example Application
The application sends e-mail notifications to users. The message text is different for regular and premium users.
The “BL” sub-package contains the business logic, it knows how to generate the email (address, subject and message) and how to call the SMTP client.
This design is maybe the easiest possible way, but it has also disadvantages:
- Re-using the business logic is not possible because it’s internally wired directly to the STMPClient
- This also makes it hard to unit-test, the dependency is hidden in the implementation
- To replace the low-level STMPClient you have to change it’s depending class SMTPNotificationSender directly
That may not be a big deal in a small application like this example, but growing applications and changing requirements require flexible designs.
Refactor towards the dependency inversion principle
To solve the coupling problem we have to invert the dependencies: The business-logic provides an interface (abstraction), that is implemented in the low-level infrastructure package.
I moved the business-logic (core) and the infrastructure into separate class-libraries. As you see, the core library has no dependencies!
The “SMTPNotificationSender” is now called “NotificationService” and receives an instance of the interface SendMail by constructor-injection. This decouples it from the low-level E-Mail sending implementation and makes to business-logic easy to test.
The “Main” routine is now responsible for wiring up the classes at startup.
And finally here’s an example for 2 test-cases using Telerik’s JustMock:
You find the full project in my GitHub Repository and also a diff between the original and the refactored version.
Summary
The dependency inversion principle is very useful to build maintainable and testable code. The instantiation (wiring up the classes) is not cluttered all over the project. The actual implementation classes can be reused and easily replaced. A DI Framework / IoC Container wich I’ll explain in the next blog post can make this even easier.