SOLID Ruby: Dependency Inversion Principle
Continuing with our series, let’s talk about the Dependency Inversion Principle (DIP). This principle states that software components should depend upon abstractions instead of implementations. What does this mean?
In the context of static languages like Java it’s easier to explain it, ’cause there are language constructs like Interface and abstract classes to provide the programmer ways to supply “abstract dependencies,” improving flexibility and testability in the process.
But what about Ruby? It’s a dynamic language and we don’t need to specify the types of the dependencies (duck typing is the single best feature a language can have, IMHO), so we get to use the DIP for free, right? Well, provided you apply some simple techniques, yes. Let’s talk about that.
Let’s get back to the example from the last article:
This code has some rigid dependencies. We’re calling the GamePriceService and JsonParserLib classes explicitly from GamePrinter#print and GamePriceService#get_price, respectively. As you can see, even if duck typing allows our code to be type-independent, we wrote the code in a way that our classes are tied to specific types through its dependencies.
A nice way to take full advantage of duck typing and gain all the benefits offered by the DIP is to make our dependencies transparent. Ruby makes this quite easy:
Here I chose to pass the dependencies through the classes’ constructors. That way we’re free from type dependency and now only depend on interfaces. Everything we need to do is ensure the supplied objects respond to the interface used by the dependent class. This is what I call “implicit interface” – we don’t need to write a special “contract class” like Java’s Interface, the protocol is defined by the uses we make of our objects.
This technique is called “Dependency Injection”. Despite similar names, it should not be confused with the DIP – injection is one way of achieving it, not the only way.
Another option is to pass the dependencies through the methods that will make use of them:
Doing this, we avoid constructors with too many parameters when that starts being a concern. Usually I’ll choose constructor injection when the existence of the class doesn’t make sense without the dependency (e.g. injecting an HTTP capable object into a class that consumes an API) and choose method injection when the use of the dependency is limited to the method itself (e.g. injecting a User object into a method that needs the username to send an e-mail). That’s subjective though, so do as you feel it’s better.
Another nice trick we can see in the code is the use of default values for parameters. This diminishes the amount of code we need to write in the callers under “normal” circumstances. Only when there’s the need for a different object to be passed in we have the ability to overwrite the default value (testing is a common case).
Speaking of testing, this is a context in which the DIP really shines. Given we now have the ability to inject the dependencies into our code, the use of test doubles (fake objects that substitute the real ones, like stubs, mocks and spies) is trivial. A good side-effect is test execution speed: given we can use doubles instead of the real dependencies, the setup and wait time should be way smaller.
Aside: Test doubles
Much like stunts doubles, we can use objects that “look” like the real ones to test our code and the interactions between each of its components. This is really interesting in some cases, especially these:
- The real code is slow or makes use of external resources not always available;
- The real code depends on a service that has no testing facility (like a payment gateway with no sandbox);
- You want to specify behavior through interaction (verifying protocols), writing unit tests that are really isolated.
For each use case there’s a kind of test double that will help you achieve your desired goals. The most common ones are:
- Stubs: these are used to replace a method call with a predefined return value. It’s the simplest use case;
- Mocks: responsible for the “real” magic of BDD, these are like stubs that can also record the interactions and messages received. Then, after the test runs, they can check whether the specified messages were received (including the number of times a message was received and the arguments passed in);
- Spies: these are like mocks, the difference here is that with mocks you write a expectation, then invoke the code that should (or should not) trigger it. With spies you keep the “normal” testing flow of invoking the code and then checking for the interactions (like the traditional assertions);
- Fakes: simpler versions of complex objects, generally “hand crafted” (not using a special tool or framework) and used when the real code would cause the tests to be too slow, cumbersome or unreliable.
Back tou our main theme, here’s a simple example of spec using the technique:
Generally speaking, if you can’t replace a dependency with a double on your specs, your code is too rigid and can take advantage of the application of the Dependency Inversion Principle.
Coming up: three for one with Open-Closed Principle, Liskov Substitution Principle and Interface Segregation Principle.