Dependency Inversion Principle

Dependency Inpervion Principle is one of the SOLID design principle. It states following two thing:

  1. High-level modules should not depend on low-level modules. Both should depend on the abstraction.
  2. Abstractions should not depend on details. Details should depend on abstractions.

Let’s consider the following bad example and find out the problems with it:

class Logger
{
    public function log(): string
    {
        return "Logger";
    }
}
 
class FileLogger extends Logger
{
    public function log() : string
    {
        return "logged in a file";
    }
}
 
class StdoutLogger extends Logger
{
    public function log(): string
    {
        return "logged in screen";
    }
}
 
class LoggerManager
{
    private $logger;
 
    public function __construct(Logger $logger)
    {
        $this->logger = $logger;
    }
 
    public function log(): string
    {
        return $this->logger->log();
    }
}

In this example. LoggerManager (high level) depends on the Logger class (low level) and it violates dependency inversion principle for tightly coupled dependency to a concrete Logger class from high level module LoggerManager. According to dependency inversion principle they should depend on abstraction. Let’s refactor this. We will convert the logger class to an interface and logger manager will depend on the interface. This accepts dependency inversion principle now.

interface LoggerInterface
{
    public function log(): string;
}
 
class FileLogger implements LoggerInterface
{
    public function log() : string
    {
        return "logged in a file";
    }
}
 
class StdoutLogger implements LoggerInterface
{
    public function log(): string
    {
        return "logged in screen";
    }
}
 
class LoggerManager
{
    private $logger;
 
    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
 
    public function log(): string
    {
        return $this->logger->log();
    }
}

Test cases

<?php declare(strict_types=1);
namespace App\Tests;
 
require_once './vendor/autoload.php'; 
use PHPUnit\Framework\TestCase;
use App\DIP\FileLogger;
use App\DIP\StdoutLogger;
use App\DIP\LoggerManager;
 
class DipTest extends TestCase
{
    public function testFileLogger()
    {
        $logger = new FileLogger();
        $loggerManager = new LoggerManager($logger);
 
        $this->assertRegExp('/logged in a file/', $loggerManager->log());
    }
 
    public function testStdoutLogger()
    {
        $logger = new StdoutLogger();
        $loggerManager = new LoggerManager($logger);
 
        $this->assertRegExp('/logged in screen/', $loggerManager->log());
    }
}
 
 

Execute test case

./vendor/bin/phpunit tests/DipTest.php PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

.. 2 / 2 (100%)

Time: 30 ms, Memory: 4.00 MB

OK (2 tests, 2 assertions)

Written on March 28, 2021