Dependency Inversion Principle
In the last article we learned about the Interface Segregation Principle. In this article we will be going over the Dependency Inversion Principle.
The Dependency Inversion Principle, is the fifth SOLID Principle for OOP.
For those new to SOLID, S.O.L.I.D Stands for:
- Single Responsibility Principle
- Open Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
This article is the fifth of a five post series that will cover the SOLID Principles.
A higher class should never depend on a lower class, but only depend on an abstraction of that class.
This principle is one of my favorite principles, because it allows us to decrease the coupling of classes, making our code overall easier to maintain, and test. Both good things!
So let's think of an Orchestra. We have a Conductor, who directs all the musicians, playing their instruments. We also have 'chairs' if you will. For instance, each instrument has a first chair, and second chair, and so on.
Let's call our Conductor our Higher Class, and our musicians lower classes. Let's further say our Conductor has a concert to perform, and he requires all his musicians to perform a piece. However, his first chair tuba player (a lower class) is out due to a tuba related injury. Now, the Conductor (a higher class) could say, well we can't perform our piece because our first chair tuba player is injured, or the conductor can temporarily promote the second chair Tuba player Ted who had nothing to do with Fred's Tuba related injury I promise!
This is the idea behind Dependency Inversion. Our Higher class isn't dependent on a lower class, it has the ability to swap out the lower class with another similar class if required, and the music still gets made.
Let's see what this looks like using our Garden Outputter examples.
Class GardenOutPutter {
private $garden;
Public function __constructor(Garden $garden)
{
//This is normal dependency injection.
$this->garden = $garden;
}
}
The class above is how were doing this in our previous examples. We are injecting our Garden
class dependency into our GardenOutPutter
class. In this example the GardenOutPutter
is our higher class, and our Garden
is our lower class.
Note: In the above example, even if we pass our Plot
class into our GardenOutPutter
class, it will still check out as a Garden
class becuase Plot
is a subclass or child of the Garden
class.
What if our garden example required a bit more flexibility though, and all of our classes we wanted to send to our GardenOutPutter
class didn't extend our Garden
class?
We need to create a GardenInterface
.
Interface GardenInterface
{
public function quantity();
}
Now our Garden
, Plot
, Field
, PlanterBox
need to implement the GardentInterface
interface. Then we need to make some slight changes to our GardenOutPutter
class.
Class GardenOutPutter {
private $garden;
Public function __constructor(GardenInterface $garden)
{
//This is dependency inversion injection
$this->garden = $garden;
}
}
Now when we pass any class that implements our GardenInterface
Interface the $garden variable will hold that class object.
Inversion of Control Containers
So far in all of our code examples in the last five articles, we have been instantiating our classes, and passing dependent classes into higher classes. However, when thinking about using a framework like Laravel, how would we pass a class into a controller?
A controller is just a class, like any other class, but it follows the front-controller design pattern. It acts like a logic gate keeper so to speak, handling requests, and formatting responses. But when we land on our index page to our website, and we load our HomeController@index
, how would we pass a dependent class to that method?
We do this through Inversion of Control Containers in Laravel. What this allows us to do is bind
a class to an interface. So in our HomeController index()
method we can do something like this:
class HomeController
{
...
private $garden;
public function index(GardenInterface $garden)
{
...
$this->garden = $garden;
}
}
This should look familiar so far. However, this isn't going to work yet, because we haven't passed a class that implements the GardenInterface
to this method. We still need to bind a class to that Interface.
To do this we do the following:
$this->app->bind('App\NameSpace\GardenInterface', function () {
return App\NameSpace\PlanterBox;
});
This code, when ran will tell Laravel, when ever you see a method requiring a GardenInterface
class, pass it the PlanterBox
class.
Now in our HomeController index()
method, our $this->garden
variable will hold the class object of PlanterBox
.
Now where does this code actually go? In Laravel 5.* in the app/Providers
directory their is a AppServiceProvider
Class. The above code will go in the register()
method in that class.
Laravel loads service providers when the application boots up, so code in the register and boot methods will be run before any controllers are loaded. This allows us to create this binding for our controllers.
Now, we have decoupled our Controllers from our Gardens. Now if we need to write a test, we can mock our garden class so our tests are only testing the HomeController
. Also, if we needed to swap our PlanterBox
class out with a Field
class later on, we just need to update our binding to point to the new class. As long as Field
is also implementing the GardentInterface
Interface it will now load a Field
Object into $this->garden
in our HomeCOntroller@index
method.
Thinking about this in a more Laravel way, instead of Garden classes we often use Eloquent Models in our controllers. So what we could do instead is do Dependency Inversion for our Models, and that way when we write tests we can mock for instance the User model, and run tests that don't interact with our DB.
There are lots of other good examples on how this is a powerful tool, but for the purpose of this article, I won't be going into further examples. However, I used Dependency Inversion and Injection extensively in my earlier articles about building a REST API in Laravel 4. While it's a bit outdated for Laravel version numbers, the same principles apply.