Perfecting Your SOLID Meal With DIP
Applying Dependency Inversion Principle as described in various sources can help you a lot, but you need to understand it's benefits, mindset, and trade-offs first.
Join the DZone community and get the full member experience.Join For Free
The codebase has improved so much lately that you’re famous now! Each day you come to WooMinus, you’re greeted with a different title: The One Dev To Satisfy Them All, The Grandparent of New Features, The Compatibility Guardian, or The Monolith Crusher. When you sit at your desk, a coffee and a croissant are waiting with a card wishing you a nice day. The helmet on top of your head was replaced by a crown. Your life is like an almost-perfect meal. We’ll make it perfect using DIP.
A Humble Request
One day, when you get into the office, your new office puppets, Ben and Tony, are kneeling next to your desk.
“What can possibly be so important that you dare waste my precious time?” you ask.
“Dear Holy Mighty Monolith Crusher! We’re coming with a humble request. May we proceed and explain it to you?”
“Just do it quick. My holy eyes should not endure the pain of looking at you for much longer!”
“So, as we discussed with other developers, the great benefits that we and rest of the company have recently gained were a result of effectively applying the SOLID principles. As we managed to research, so far, only the first four of those were broadly applied in our codebases. Could you show us a little more mercy and help us benefit from the last one – the Dependency Inversion Principle?”
“Fine. Gather everyone. Let my blessing help this company once again.”
Dependency Inversion Principle
We’ll start with a grasp of the basics. The principle tells us two things:
- 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.
This means that when writing our high-level business logic, we don’t want to depend directly on low-level stuff like databases, file systems, network connections, provider contracts, and such. Instead, we should expose an abstraction that represents a higher-level business need and implements it using these low-level mechanisms. A module in the principle can be as small as a single class and the abstraction we’re talking about would be an interface or an abstract class. In such a case, the dependency would be indicated by an import statement. Our high-level module should import and use only the interface and have no knowledge of its implementation.
That should be enough, I don’t want to go too much over the basics here. Those who’d like to see basic implementations of this principle should check out Dependency Inversion in Java on Tidy Java, the (in my completely unbiased opinion) best quality-coding blog in the universe, and the chapter about DIP in Uncle Bob’s PPP book.
Benefits of Applying DIP
To take full advantage of the principle, one should fully understand its benefits. Otherwise, one would have to treat it as dogma, and we all know that programming should stay as far from being a religion as possible. So here are the main benefits of applying the Dependency Inversion Principle:
- High-level policy code can be reused across applications, regardless if they use the same low-level mechanisms like databases, operating systems, or external providers
- Changes in low-level details do not propagate to high-level code, causing it to change
- High-level policies and low-level details can be developed and deployed independently
What a Good Abstraction Looks Like
Simply placing an interface between the high-level and low-level code is not enough to gain all the benefits mentioned above. One (anti-)example would be an interface that mimics the interface of an underlying low-level mechanism. In such case, a developer who updates low-level stuff would probably adjust the high-level stuff, too, and the interface would be effectively useless. We want to go another way. We want to hide the underlying mechanism and stay agnostic to its changes, as long as it fits our business need. The most obvious things that come to my mind in terms of hiding the mechanism are:
- Using identifiers from the problem domain instead of the low-level domain
- Using primitives or custom classes as arguments
- Not throwing exceptions of types coming from the low-level mechanism
I’d capture all of the above in a DIP mindset: the abstraction should represent what the high-level policy requires from the low-level stuff. In a sense, the abstraction belongs to the high-level code.
The No-Interface Way
While reading about the Dependency Inversion Principle, and interfaces in general, I have come across an interesting claim: class’ public methods are the class’ “interface” and therefore, instead of creating an interface with a single implementation, one should simply create an ordinary class with the same methods. This way, you avoid the overhead of indirection and an extra source file associated with declaring the interface separately.
It seems like a sleek and reasonable, thus appealing option. I think that legacy codebases add their huge bit to this “appeal” — they’re often full of needless interfaces and abstract classes, created for the sake of reusability, modularity, and extensibility, while in reality holding none of these properties. So, is this a viable option or not?
I’ll once again try to refrain from religion and fall back to actual benefits. By giving up on using an interface, we’re losing two of the mentioned ones. Firstly, we cannot reuse the high-level code so easily because it drags along dependencies to all the low-level stuff. Secondly, we cannot independently deploy the interface and its implementation because they’re the same thing. What we keep is only the benefit of high-level code being agnostic to low-level changes.
Putting things this way, the problem is reduced to this simple question: Do we actually care about code reuse and/or independent deployability? I can’t answer this one for you, although I expect most could answer negatively. Also, there’s nothing stopping you from changing your decision later, as you’re aware of the trade-offs in place.
SOLID As A Rock
That would be it for Dependency Inversion Principle and the whole SOLID series. Apply each of the rules and your code will be solid as a rock!
This was the last post featuring Ben and Tony. If, by any chance, you’d like to see those characters and their office stories in some future texts, let me know in the comments!
Published at DZone with permission of Grzegorz Ziemoński, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.