What is the Dependency Inversion Principle exactly?
Dependency Inversion Principle (DIP for short), is a guideline that you can use to design a software system. It doesn't tell you HOW to do it exactly. There might be MANY WAYS of actually following the DIP-principle. I'll show you some examples below.
This principle isn't about any implementation details. Instead it is more about defining abstractions upfront and make your high-level classes and low-level classes depend on them. That is where the "inversion" in this principle comes from.
Instead of thinking about your design from the perspective of high-level classes, that are build out of low-level classes. Try to define abstractions upfront and make your high-level classes and low-level classes dependend on the abstractions you defined.
The definition
The DIP-principle is a requirement of your software design: high-level modules should not depend on low-level modules, both should depend on an abstraction. How you want to actually achieve this is up to you!
So what are ways to follow the DIP you might ask?
"Program to an interface, not concrete implementations" is also a guideline, but is more specific: use interfaces or abstract classes that your code should depend on. How you use those abstractions is still up to you. This is one of the ways to achieve the DIP.
interface ProductInterface
{
public function getPrice();
}
class Customer
{
private array $products;
public function addProduct(ProductInterface $product)
{
array_push($this->products, $product);
}
public function checkout()
{
$total = 0;
foreach ($this->products as $product)
{
$total += $product->getPrice();
}
// some checkout logic that uses the price of the products.
}
}
Dependency Injection (DI for short) is another way to do it. Here you typically get the module (dependency) injected through a constructor that has a reference to an abstraction of the dependency as the parameter type.
interface CheckoutModuleInterface
{
}
class customer
{
public CheckoutModuleInterface $checkoutModule;
// some methods to add Products are omitted.
public function __construct(CheckoutModuleInterface $checkoutModule)
{
$this->checkoutModule = $checkoutModule;
}
public function checkout()
{
$this->checkoutModule->checkout($this->products);
}
}
In conclusion:
The difference between the two is that Dependency Inversion Principle is a higher level design principle that guides you to create modules that are loosely-coupled (don't depend on each other, but depend on abstractions). It's a way of thinking about software design, not really how you should implement it. While Program to an interface, not concrete implementations or Dependency Injection are examples of how to achieve the dependency inversion principle. And are implementation details.