PHP Course : SOLID - Dependency Inversion

PHP Course : SOLID - Dependency Inversion


Feb 28 2019, 14:18 in Web Development

Last but not least, the Dependency Inversion Principle!

There is a common misunderstanding that "Dependency Inversion" is similar to "dependency injection." However, the two are not the same, as I explained in What Is Dependency Injection.

I briefly explained that Dependency Injection is all about injecting parameters in methods, and that Dependency Inversion is something else.

Note that Dependency Inversion rely a lot on Dependency Injection, but they are not the same thing.

Now let's review this last Principle entirely. This might be the most difficult one to understand for some people.

Definition

This principle states that your code should depend on abstractions, not on concretions.

Or in other words : High Level modules should not depend upon Low Level modules. Instead, they should depend upon abstractions. But the Low Level modules, too, should depend upon abstractions.

But once again, if you're like me, those definitions just fly over your head and you don't really know what to do with this.

Well, just remember that the goal is always to decouple the code.

High Level & Low Level code

What exactly does that mean?

High level code is any code that isn't concerned with the details (eg: does not care about how to connect to a database).

However, Low Level code is more concerned with details and specifics (eg: cares about how to connect to a database).

Those High Level classes should never depend upon Low Level classes.

Or, to put it in another way, a class should never be forced to depend upon a specific implementation. Instead they should rely on a Contract, an Interface.

Example

Imagine you have something like a TV or a Lamp, or a Radio. All those things run on electricity, right? And thus need power and have a plug for that.

In order to plug those object to your electricity source, you don't have to manually wire the cables together into your house electricity source, right? Instead, you have outlets. Think as these outlets as an interface.

In other words, those electric devices don't need to understand explicitely how to connect to the electricity source and how they are powered. They simply rely on the fact that there is some interface that they can hook into and that will give them what they need.

Hopefully this makes sense 😄 but let's take it one step further.

Your house specifically provide outlets (the interface). So your house owns the outlets, owns the abstraction. So your house is saying "anything that wants to receive power absolutely must conform to this outlet interface that I provide".

It also means that the devices (TV, lamp, radio), they do not own that interface. They conform to it.

This really backs up the idea that High Level code depends upon abstraction, but low level code, too, depend upon abstraction.

The interesting part is when you start asking "which part owns this abraction?".

Code Example

Imagine you have a Class responsible for sending a Welcome Email when a user register on your website. We will first send the emails using SMTP directly from the server.

class SendWelcomeEmail
{
    private $mailDriver;

    public function __construct(SMTPMailDriver $mailDriver)
    {
        $this->mailDriver = $mailDriver;
    }
}

✋ Now, take a minute and think about the downside of this code. Try to think about every pros and cons about this implementation. When you're finished, continue reading ✋

Okay, first we ask the question: "Why does SendWelcomeEmail has any interest whatsoever in what our Mail Driver is ?"

Yeah, we are using SMTP, but why should SendWelcomeEmail care about that?

Following the Dependency Injection pattern, we indeed inject the mail driver into the constructor of the class, but as I explained, Dependency Inversion is not the same thing. And this, in fact, violates the Dependency Inversion Principle. How come?

Well, High Level modules (SendWelcomeEmail class) should not depend upon Low Level modules (SMTPMailDriver class). Instead, SendWelcomeEmail should depend on abstraction, not concrete implementation.

If this still sounds confusing, try to think about the idea of knowledge. Does SendWelcomeEmail need to know about SMTP, or how to actually send the email? Imagine you want to use Mailgun or Postmark instead, you shouldn't have to change SendWelcomeEmailat all.

Look at this solution:

interface MailDriverInterface
{
    public function send();
}

class SMTPMailDriver implements MailDriverInterface
{
    public function send()
    {
        return 'use php mail() method';
    }
}

class SendWelcomeEmail
{
    private $mailDriver;

    public function __construct(MailDriverInterface $mailDriver)
    {
        $this->mailDriver = $mailDriver;
    }
}

Thanks to this implementation, both High Level & Low Level modules depends upon an abstraction (an interface).

Inversion Of Control

IoC is certainly a term you've heard of. How does it connect to our principle?

Based on the example above, try to ask yourself: "Now, who owns MailDriverInterface?" Does SMTPMailDriver owns it? Or does SendWelcomeEmail owns it?

Yes SendWelcomeEmail depends upon MailDriverInterface, however, what if we wanted to update SMTPMailDriver ? Maybe we need to change the way it works.

And maybe when changing this class, the changes will cascade into changes into MailDriverInterface. And then it would cascade into any class that depends upon this interface.

And that what brings us to IoC, Inversion of Control. In other words: who exactly is in control here? To help us answer this question, let's think again about our electronic devices:

Does the Tv and the Lamp owns the way they fetch power? Does the Radio dictates how it connects to the outlet? No, it's the other way around. So in fact, they do now own this abstraction.

Conclusion

Dependency Inversion is not Dependency Injection.

However, Dependency Injection gives us a methodology to implement Dependency Inversion

In the end, what you really must enforce in your application to follow this principle, is to make sure that your code does not rely on specific implementation. They should rely on abstraction, exactly like the code above.

Hope it helps!


Share:
Like:

Disable AdBlock on this domain and offer me a cup of coffee :)