This wraps up the series of posts on SOLID Code with Emergent Design.
Dependency Inversion Principle
"Separate interfaces so callers are only dependent on what they actually use"
The SOLID principles really boil down to managing your dependencies between classes. And, by "manage", we really mean "get rid of those suckers."
Removing dependencies (aka, de-coupling) has a number of advantages. For starters, your code becomes significantly easier to understand and maintain because it is possible to understand each piece in isolation from the rest. It’s also easier to reuse your code in other applications because it’s not hopelessly entangled in your own application’s specifics. And, of course, the fewer dependencies there are, the fewer chances you have of changes to one part of the code inadvertently breaking a different part of the code.
Nonetheless, we often find ourselves writing code where higher level modules (the ones with our business smarts) depend on lower level modules.For example, suppose you have a program that takes input from the keyboard and outputs it to the printer. You might have a Copy class that calls methods in Keyboard and Printer classes as such:
Perhaps you’ve even read advice that says this is how we should do design: have your higher layers depend on your lower layers. But the reality is that this is the exact opposite of what you want. What you’ve just done in designs like the one on the right is to make your business logic (that which is most sacred) dependent on EVERYTHING ELSE in the application. Imagine if you had to rewrite your application every time a user changed their keyboard. That would be pretty ridiculous, right? This is why our operating systems abstract these nitty gritty details from us so that our applications don’t have to worry about them.
And, that is exactly what we want to do with the concrete details in our application. Abstract them. And when we do this – it has the effect of inverting (or flipping) our dependencies. So, now – to go back to our copy program, what we should do is modify Copy class to only depend on abstractions (e.g., interfaces or abstract base classes). And then the so-called concretions (concrete input & output classes) implement those interfaces, as such below:
Now, if the type of keyboard changes, or if we want to expand the types of input and output devices supported, we can make all of these changes without impacting Copy (imagine replacing Copy with some sufficiently complex piece of business logic in your organization that you really don’t want to take any unnecessary risks of breaking).
So, I don’t know about you, but I had some questions when I first learned of this:
Ugh, isn’t this going to create billyuns and billyuns of Classes?
Uncle Bob says not to worry. The number of files created shouldn’t even be a consideration with modern programming languages.
Okay, but… are we still being agile if we have to create all of these classes?
Remember back in the Open-Closed Principle where we said that if you create abstractions for things that don’t wind up changing, you’ve wasted a lot of time? Isn’t the same true with our Copy program? Isn’t adding all those abstractions a bit overkill sometimes?
I think it is. And I think that it’s fair to only create abstractions when we have more than one concrete class performing the same behavior. So, for example, we might start with our simple design where Copy depends on Keyboard and Printer. But then, as soon as we have to deal with different types of readers – such as Barcode scanners – we add the ReaderInterface (the same would hold true for WriterInterface the first time we need to support multiple outputs).
How does your app do anything if nothing knows about (depends upon, points to) the concrete details?
Good question! Clearly, something has to be aware of whether we’re using a keyboard or a barcode scanner, otherwise you can never even instantiate them. For this, we can use the Abstract Factory pattern, which allows us to separate our dependence on concrete details from the rest of the application.
I have obviously abstracted a lot of the details behind these principles to introduce the concepts. I think they’re invaluable tools for emergent design as we’re continuously refactoring. If you’d like to learn more, I recommend you go straight to the source himself, Robert Martin. He’s written on these principles in his om/Software-Development-Principles-Patterns-Practices/dp/0135974445/" target=_new>Agile Software Development books, as well as making a wealth of knowledge available online, including chapter excerpts on the SOLID principles.