🖋️
This article is one part of my series on Robert (Bob) C. Martin's SOLID principles.

And so we've arrived at the end of the series with the Dependency Inversion Principle.

We're going to spend quite a while defining this one, because it has something of an identity crisis and I think it's important we understand what the principle is trying to convey before we move onto examples and explanations. And because the canonical definition is hot garbage.

Strap in!

Definition

Bob's first definition of DIP was way back in 1996, in a C++-focused article by the same name (available via archive.org). It has two parts:

A. High level modules should not depend upon low level modules. Both should depend upon abstractions.

B. Abstractions should not depend upon details. Details should depend upon abstractions.
A man reading a book to a young girl on her bed with cosy lighting. He puts the book down and looks up, confusion on his face.

I read this back and forth, confounded, before delving deeper into the article until it hit me: this contains very specific language.

Terminology is important

By "abstractions," Bob explicitly refers to abstract classes or interfaces. These two concepts are similar enough that I'll refer only to interfaces going forward, and Ruby technically has neither (check out the previous post on OCP and LSP to see how interfaces fit into dynamic languages). These interfaces define "what" should be done.

By "details," Bob means what I would normally call "implementation details," i.e., the technical bits which define "how" something should be done.

This clears up part (B). Abstractions and details, or interfaces and implementation details, should only depend on, or call methods on, other abstractions.

But what about Part (A) and Inversion?

Use of the word "inversion" is confusing. It seems related to a common (in 1996) pattern in which high-level API design was influenced by low-level APIs. That is: at a high level, modules call low-level methods (e.g. file.write_to_disk) as opposed to abstractions (file.save).

Already in that 1996 article introducing DIP, Bob knew his choice of the word needed explanation:

One might question why I use the word “inversion”. Frankly, it is because more traditional software development methods, such as Structured Analysis and Design, tend to create software structures in which high level modules depend upon low level modules, and in which abstractions depend upon details.

— Robert Martin

My hypothesis is that Bob was busy writing about DIP and, distracted, began ranting about API design going from bottom to top rather than top to bottom. This rant was then shoe-horned into a principle that otherwise solves the problem he's ranting about far more elegantly.

There is a case to be made for having high-level code defining interfaces for lower level code as opposed to the inverse, but I think it's a story best told elsewhere.

Further evidence that inversion doesn't belong in this principle is in Martin's more recent (2017) book, Clean Architecture. In the chapter entitled "DIP: THE DEPENDENCY INVERSION PRINCIPLE," the word "inversion" only appears as part of "dependency inversion principle."

In that book, Bob instead discusses the various levels of volatility of abstractions and relaxes his hard-line stance against depending on details. That is to say: try to depend on stable interfaces, but it's okay to depend on (stable) implementation details as well. Clean Architecture is a much better read than the original article.

Both "Inversion" in the name and the entire part (A) of the definition should be ignored. (B) covers it in its entirety. This ties the Dependency Inversion Principle for title of the worst-named principle in SOLID.

Let's try again

DIP can be abbreviated to

All code should depend on stable abstractions (or explicit interfaces).

Dependency Inversion Principle might instead be referred to as

The Interface for Everything* Principle

*Okay, almost everything if we want to consider stable implementation details as well. But as a general principle, it's not absolute.

Enough theory. We need an example.

Concrete dependencies = tight coupling

đź’­
Like most of these principles, demonstrating it in Ruby takes some imagination. We don't have interfaces or abstract classes, but we've learned that we do have inherent interfaces as contracts (again, see LSP). Please bear with me as I stumble through this.

Suppose we develop a hack and slash game. The player selects from a sword-wielding warrior, bow-toting archer, or magic-casting wizard nd descends into a dungeon to fight nasty beasties.

class Warrior
  def swing_sword = puts "Swingin' muh sword!"
end

class Archer
  def fire_arrow = puts "Firing an arrow!"
end

class Wizard
  def cast_spell = puts "Wingardium leviosaaaaah"
end

A stupid-simple set of characters.

Each character attacks in a unique way, which is logical given that each has different weapons and skills. They are instructed to attack:

class Game
  # ...
  def attack
    return swing_sword if character.respond_to?(:swing_sword)
    return fire_arrow if character.respond_to?(:fire_arrow)
    return cast_spell if character.respond_to?(:cast_spell)
  end
end

Well, this sucks. But why does it suck?

It sucks because Game is depending on concrete implementation details of each character. It needs to know what each character's attack sequence is in order to instruct it to carry out that sequence.

This is already problematic with a simple implementation, but it quickly spirals out of control as we introduce more moving parts:

class Archer
  # ...
  def nock_arrow
    return if @arrow_nocked || @arrows.zero?
    
    @arrows -= 1
    @arrow_nocked = true
  end

  def fire_arrow
    return unless @arrow_nocked

    puts fire_arrow = puts "Firing an arrow!"

    @arrow_nocked = false
  end
end 

Now, firing an arrow requires that we first nock the arrow.

What does that do to our Game?

class Game
  # ...
  def attack
    return swing_sword if character.respond_to?(:swing_sword)
    
    character.nock_arrow if character.respond_to?(:nock_arrow)      
    return fire_arrow if character.respond_to?(:fire_arrow)
    
    return cast_spell if character.respond_to?(:cast_spell)
  end
end

It needs to know even more about the Archer character. And what if the wizard can nock and fire arrows but prefers to cast spells? In our implementation, he's stuck playing a glorified archer.

Consider that this example is contrived: it is still exceptionally simple compared to real-world code. Depending on interfaces becomes only more important as complexity grows.

So the game is very tightly coupled to the characters. We can decouple them with an interface.

Wanted: attacker. Must attack things.

The abstraction we want here is for attack. The game doesn't care how each character attacks, it only cares that it responds to attack. Characters themselves need to handle the details of attacking.

In languages that support them, we would create an IAttack , Attacker, or Attacks interface, with just one method: attack. In languages that don't support interfaces, the programmer must convey by other means that characters all need to respond to attack — that is the contract interface consumers sign up to.

The implementations can continue to use existing methods for details, so long as they are not part of the agreed-upon interface. That is, nock_arrow and fire_arrow are not part of the attack interface, but the Archer can still call them from attack.

class Warrior
  def attack = swing_sword
  
  # ...
end

class Archer
  def attack 
    nock_arrow
    fire_arrow
  end

  # ...
end

class Wizard
  def attack 
    swish_wand
    cast_spell
  end

  # ...
end

Opting into the "attack" interface

Now, Game doesn't need to know anything about any character and can send an attack message to all of them equally:

class Game
  def attack = character.attack
end

Interfaces = flexibility

Introducing interfaces may at first seem like an application of rigidity because classes must conform to our expectations without exceptions.

In fact, it's the opposite.

Allowing characters to define their own attack scenarios was easy until it came to using them. They required its consumer — Game — to study character implementation details. They made Game rigid and inflexible.

By applying the attack interface, however, Game won't have to change when we introduce a Druid and Assassin in a critically-acclaimed expansion pack, provided they follow the same interface. What's more, relying on the attack interface arguably increases its conformance to all of the other SOLID principles.

In the real world, consider how something like an ORM is an interface which abstracts away data access details. At a high level, programmers call User.all to receive a list of all users; details could involve generating SQL, querying a REST API, or really anything so long as a list of users is returned when that method is called.

What about high-level and low-level code?

We established that DIP's distinction between high- and low-level code is redundant, but let's touch on that briefly.

Assume each character has a visual representation — a "model". This gets displayed on the player's screen.

A level 93 wizard can be displayed on a 17" Sony Trinitron just the same as it appears on a 6.9" iPhone. How it's gets displayed certainly doesn't matter to our Game. From an abstraction at a high level all the way down to lighting up the right pixels on the screen, we pass through numerous interfaces which each ensures compatibility with a range of software and hardware. Methods like display_frame or draw_pixel might be part of these interfaces.

It's really quite incredible how far removed the final experience is from the enormous stack of interfaces that work together to make it up.

Anyway, let's come back down to earth.

Summary: interfaces good

On the one hand, my gut tells me that this principle is instinctual to software engineers. On the other hand, it may only seem so when we break it down to contrived examples like those above.

In languages like Java and C#, creating interfaces is a conscious decision that's hammered into practitioners from day one; at least, that was the case back in my uni days. Read any library code in these languages and you'll see an almost ludicrous number of interfaces, each more specific than the last.

In the duck-typed world of Ruby, we need to make more effort to think in terms of interfaces — behaviour and responsibilities — rather than vibe-coding our way to production. Perhaps it is here that we miss out on some of the more formal learnings in our field, and that placing emphasis on composable behaviours may lead us to more maintainable software.

Taken to their extremes, in one corner we have the Java developer who writes an interface for every sniffle and sneeze before shipping trivial changes, and on the other is the Ruby developer whose code is more like a stream of consciousness than a well-considered system. I've worked with engineers at both ends of this spectrum.

As with most things, the "just right" porridge is in the middle.

Software is not permanently set in stone, so if you can do without interfaces early on — if you have no need for them, and can safely say they add no value — then don't force writing them. When and if you do need them, — when you have a second character class, for example — refactor.