SOLID Ruby: Dependency Inversion Principle

Share this article

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: [gist id=”3001503″] 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: [gist id=”3001505″] 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: [gist id=”3001511″] 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: [gist id=”3001641″] 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. Additional references: Make Your Dependencies Translucent with Default Parameters Test doubles on Wikipedia Mocks aren’t stubs, by Martin Fowler

Frequently Asked Questions (FAQs) about Dependency Inversion Principle in Ruby

What is the main difference between Dependency Inversion and Dependency Injection?

Dependency Inversion and Dependency Injection are two concepts that are often confused, but they serve different purposes. Dependency Inversion is a principle that guides us to avoid dependencies on concrete types and instead depend on abstractions. It’s a way of decoupling software modules. On the other hand, Dependency Injection is a technique to implement Dependency Inversion Principle. It’s a way to provide the dependent object to a class, typically by using a constructor, a setter, or an interface.

How does Dependency Inversion Principle relate to SOLID principles in Ruby?

The Dependency Inversion Principle is the “D” in the SOLID principles of object-oriented programming. SOLID is an acronym that stands for five design principles: Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion. These principles guide developers to design and organize code in a way that’s easy to manage, understand, and maintain. The Dependency Inversion Principle, in particular, helps to decouple high-level modules from low-level modules, making the system more flexible and adaptable to changes.

Can you provide a practical example of Dependency Inversion Principle in Ruby?

Sure, let’s consider a simple example. Suppose we have a Book class and a Library class. Without Dependency Inversion, the Library class might directly instantiate and manage Book objects. But with Dependency Inversion, we would instead pass an array of Book objects (or an abstraction of it) to the Library class, making it independent of the concrete Book class. Here’s how it might look:

class Library
def initialize(books)
@books = books
end

def list_books
@books.each do |book|
puts book.title
end
end
end

class Book
attr_reader :title

def initialize(title)
@title = title
end
end

books = [Book.new('Moby Dick'), Book.new('War and Peace')]
library = Library.new(books)
library.list_books

In this example, the Library class doesn’t depend on the concrete Book class. Instead, it depends on an abstraction (an array of objects with a title method), which is in line with the Dependency Inversion Principle.

Why is Dependency Inversion Principle important in Ruby programming?

Dependency Inversion Principle is crucial in Ruby programming as it helps in reducing the coupling between code modules. By depending on abstractions rather than concrete implementations, modules become more independent, flexible, and adaptable to changes. This makes the code easier to test, maintain, and extend. It also promotes code reusability as modules can be easily swapped or replaced without affecting other parts of the system.

How does Dependency Inversion Principle help in testing and debugging?

Dependency Inversion Principle makes testing and debugging easier by reducing the dependencies between modules. When modules depend on abstractions, it’s easier to provide mock or stub objects during testing. This allows for isolated unit tests, where each module can be tested independently without needing to set up and manage complex dependencies. It also makes debugging easier as issues can be traced and isolated to specific modules, reducing the impact of changes and errors on the overall system.

Can Dependency Inversion Principle be used with other programming paradigms?

Yes, while Dependency Inversion Principle is often associated with object-oriented programming, it can also be applied to other programming paradigms. The key idea is to reduce dependencies between modules by depending on abstractions rather than concrete implementations. This can be achieved in various ways, depending on the specific paradigm and language features.

What are some common misconceptions about Dependency Inversion Principle?

One common misconception is that Dependency Inversion Principle is the same as Dependency Injection. While they are related, they are not the same. Dependency Inversion is a design principle that guides us to depend on abstractions, while Dependency Injection is a technique to implement this principle by providing dependencies to a module. Another misconception is that Dependency Inversion Principle always requires interfaces or abstract classes. While these are common ways to define abstractions in some languages, it’s not always necessary, especially in dynamic languages like Ruby.

How does Dependency Inversion Principle affect performance?

Generally, the impact of Dependency Inversion Principle on performance is minimal. While there might be a slight overhead due to the use of abstractions and indirection, it’s usually negligible compared to the benefits in terms of code quality, maintainability, and flexibility. However, as with any design principle, it’s important to consider the specific context and requirements, and not to overuse or misuse it.

Can Dependency Inversion Principle lead to more complex code?

If not used properly, Dependency Inversion Principle can indeed lead to more complex code. For example, if abstractions are not well-defined or if there are too many layers of indirection, it can make the code harder to understand and maintain. However, when used appropriately, Dependency Inversion Principle can actually simplify the code by reducing dependencies and making modules more independent and reusable.

What are some best practices when applying Dependency Inversion Principle in Ruby?

Some best practices when applying Dependency Inversion Principle in Ruby include: defining clear and meaningful abstractions, keeping modules small and focused, using Dependency Injection to provide dependencies, and writing tests to ensure that modules work correctly with different implementations of their dependencies. It’s also important to remember that Dependency Inversion Principle is just one of many design principles, and it should be used in conjunction with others to achieve a well-designed and well-structured system.

Lucas HúngaroLucas Húngaro
View Author

I'm a 27 years old software developer living in São Paulo, Brazil. I've been working with all kinds of platforms and languages since 2001 to finally fall in love with Ruby in 2007 and never look back. I'm currently working as Senior Software Developer at Elo7.

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week