SOLID OOP Development: Dependency Inversion Principle
In this guide we’re going to walk through the final element of the SOLID Development pattern, the: dependency inversion principle.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

In this guide we’re going to walk through the final element of the SOLID Development pattern, the: dependency inversion principle.

If you have had a difficult time understanding some of the other SOLID guides you’ll be happy to know that this principle is probably the most straightforward.

Dependency Inversion Principle in the Real World

The reason why I think that the dependency inversion principle is easy to understand is because it relates to a real world pattern. Before we jump into the code we’re going to look at how a large company is structured.

Let’s imagine that you’re the CEO of Coca Cola. As the CEO of a multi billion dollar company you will have a number of responsibilities. Some of these tasks might be:

  • Managing shareholders
  • Making strategic acquisitions
  • Deciding which markets to enter
  • Along with a number of other high level decisions

Now let’s look at what you wouldn’t do as the CEO of Coca Cola. You wouldn’t:

  • Drive a truck and make product deliveries
  • Pick SEO keywords for the corporate website
  • Work on tax rate calculations for the accounting system
  • You get the idea…

As a CEO it would be your job to manage the organization and delegate responsibilities to the executives that report to you. If you spent your time with low level work like making deliveries to 7-11 you wouldn’t be able to properly manage the company.

Dependency Inversion Principle Definition

In a nutshell this is how the dependency inversion principle works.

large

The definition for the dependency inversion principle is that:

“High level objects should not depend on low level implementations”

In the same way that the Coca Cola CEO shouldn’t double as a truck driver, high level code shouldn’t perform low level duties.

Dependency Inversion Principle Code Example

For our code example we’re going to take a look at how the Ruby on Rails framework uses the ActiveRecord module.

require "active_support"
require "active_support/rails"
require "active_model"
require "arel"

require "active_record/version"
require "active_record/attribute_set"

module ActiveRecord
  extend ActiveSupport::Autoload

  autoload :Attribute
  autoload :Base
  autoload :Callbacks
  autoload :Core
  autoload :ConnectionHandling
  autoload :CounterCache
  autoload :DynamicMatchers
  autoload :Enum
  autoload :InternalMetadata
  autoload :Explain
  autoload :Inheritance
  autoload :Integration
  autoload :LegacyYamlAdapter
  autoload :Migration
  autoload :Migrator, "active_record/migration"
  autoload :ModelSchema
  autoload :NestedAttributes
  autoload :NoTouching
  autoload :TouchLater
  # Tons of other method calls

ActiveRecord is a powerful module that allows applications to interface with the database.

What do you think would have happened if the developers who built the Rails framework attempted to place specific implementation details in the framework? For example, what if they added a database query for users? It would essentially render the framework useless, because it would only work if developers built out a User class that matched the vision of the Rails developers.

Thankfully the Rails dev team chose to follow the dependency inversion principle. If you analyze the ActiveRecord module source code you’ll see that the code is simply concerned with high level functionality. This means that ActiveRecord will let you do things such as:

  • Search for records in a database
  • Allow you to call data validations
  • Utilize callbacks for automated behavior
  • And additional high level tools
  • Inherited Classes to Manage Implementation

So what does the other side of the equation look like? If the parent class, in this case ActiveRecord, manages the high level behavior, let’s take a look at a child class.

class User < ActiveRecord::Base
  has_many :posts
  has_many :audit_logs

  has_many :hands_associations, class_name: 'Hand'
  has_many :hands, through: :hands_associations
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  validates_presence_of :first_name, :last_name, :phone, :ssn, :company

  PHONE_REGEX = /\A[0-9]*\Z/

  validates_format_of :phone, with: PHONE_REGEX

  validates :phone, length: { is: 10 }
  validates :ssn, length: { is: 4 }
  validates_numericality_of :ssn

  def full_name
    last_name.upcase + ", " + first_name.upcase
  end
end

Here is a class called User that inherits from ActiveRecord::Base. This class contains implementation details, such as:

  • Listing out specific attributes
  • Mapping the User to other models in the application
  • Defining data validations for attributes that the User class manages

Recap

So in review, in our code example:

  • The ActiveRecord module is like the application’s CEO. It manages high level functionality, with zero implementation details. This allows the module to be used for any type of application and for all models inside an application.
  • While the User class is like one of the app’s workers. It focuses on the low level implementation details. It listens to the ActiveRecord parent class and follows the guidelines laid out. Much like how an employee listens to a CEO’s instructions.

Overall the dependency inversion principle is a powerful design pattern that allows you to build scalable code that can be leveraged throughout an application.