Dependency Injection Explanation
Definition
The best definition of dependency injection that I found online is by James Shore:
"Dependency injection means giving an object its instance variables. Really. That's it."
Example
class Computer
def initialize
@graphics_card = Nvidia::GeForce.new
end
def display_graphics
@graphics_card.generate_images
end
end
In the example above, there are unnecessary dependencies.
An instance of a Computer must use an Nvidia graphics card. What if my future computers use AMD graphics cards? This will require me to change the name of the graphics card I use in my Computer code. Making instances of Computer depend on a certain graphics card also reduces the code’s reusability.
To fix this, we’ll use dependency injection.
class Computer
def initialize(gfx_card)
@graphics_card = gfx_card
end
def display_graphics
@graphics_card.generate_images
end
end
See how simple the change was? All I did was, as James Shore would put it, give an instance
of Computer its @graphics_card
instance variable. Now I can swap out Nvidia graphics
cards with AMD graphics cards easily.
# nvidia/geforce.rb
module Nvidia
class GeForce
def generate_images
puts "Generating images from a GeForce"
end
end
end
# amd/radeon.rb
module AMD
class Radeon
def generate_images
puts "Generating images from a Radeon"
end
end
end
# gaming_pub.rb
computer1 = Computer.new(AMD::Radeon.new)
computer2 = Computer.new(Nvidia::GeForce.new)
computer1.display_graphics
=> "Generating images from a Radeon"
computer2.display_graphics
=> "Generating images from a GeForce"
Because of duck typing, Computer’s display_graphics
instance method doesn’t care if
the graphics card is an instance of Nvidia::GeForce or AMD::Radeon. As long as the object
responds to display_graphics messages
, any will do.
Furthermore, dependency injection allows me to test the graphics card that my computer uses.
For example, when an instance of Computer calls display_graphics
, I want to ensure generate_images
was
called on @graphics_card
and not some integrated graphics card.
# computer_test.rb
def test_should_generate_images_when_displaying_images
geforce = mock()
geforce.expects(:generate_images)
computer = Computer.new(geforce)
computer.display_graphics
end