- Read Tutorial
- Watch Guide Video
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.
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.