DIP: The Dependency-Inversion Principle

Decoupling high-level modules from low-level details

Abu Jobaer

--

Note: This publication demonstrates the Dependency-Inversion Principle in TypeScript and PHP.

DIP stands for Dependency Inversion Principle. It is one of the principles of Object-Oriented Design. Robert Cecil Martin¹ (widely known as Uncle Bob) introduces several principles in his famous book:

Agile Software Development, Principles, Patterns, and Practices

He describes five OOD principles in this book. They are collectively known as the SOLID Principles. These principles have received wide attention in the software industry. The Dependency Inversion Principle is the last one of the five principles.

Uncle Bob defines the Dependency Inversion Principle in his book. The definition is as the following:

A. High-level modules should not depend on low-level modules. Both should depend on abstractions.

B. Abstractions should not depend on details. Details should depend on abstractions.

Eric Freeman and Elisabeth Robson, in their Head First Design Patterns book, reference this definition in a concise form. They say:

Depend upon abstractions. Do not depend upon concrete classes.

You may guess somewhat what the DIP is about. It says high-level modules or classes should NOT depend on concrete classes (low-level classes). Again, it says high-level and low-level classes both should depend on the abstractions.

Concrete classes are classes that have implementation details.

These abstractions should not depend on implementation details, rather, implementation details should depend on abstractions.

Violating DIP: High-level Modules or Classes

First, we will see what the high-level modules are. The high-level modules are Client classes. What are client classes? In our case, the clients classes are the classes that have dependencies.

Then, what are low-level modules? The dependent classes are low-level modules or classes.

Do not mess up with Dependency Injection (DI). DIP is completely different from DI. DIP may have a similarity, in terms of appearance, with DI but they are totally different from each other in terms of purpose.

Let us see a code example to make it clear.

// TypeScritpt

class SocialPoster {
private twitter: Twitter;

// Notice the "Twitter" dependent class here
constructor(twitter: Twitter) {
this.twitter = twitter;
}

post(data: PostData): void {
this.twitter.post(data);
}
}
// PHP

class SocialPoster
{
private Twitter $twitter;

// Notice the "Twitter" dependent class here
public function __construct(Twitter $twitter)
{
$this->twitter = $twitter;
}

public function post(array $data): void
{
$this->twitter->post($data);
}
}

In the code above, the SocialPoster is the high-level class and the Twitter class is the low-level class. The DIP says high-level classes should not depend on low-level classes. But we have done so in the code above. What is the problem with it? Can you find anything in the code that seems troublesome to you? Check out below:

  • As the Twitter class is a concrete class, it is tightly coupled with the SocialPoster class. It violates the loose coupling principle.
  • Any changes to the Twitter class will make the SocialPoster class change. It breaks the encapsulation pillar of the OOP.
  • The SocialPoster class is not reusable. This hits one of the advantages of the OOP: reusability.
  • The SocialPoster class is limited to posting on Twitter social media. It could have made a $socialMedia parameter polymorphic, thus, this breaks the polymorphism pillar of the OOP.

Depending on the low-level Twitter class, the high-level SocialPoster class violates the Dependency Inversion Principle (DIP).

So what is the solution? The solution is somewhat tricky. What we have to do is to invert the dependency. How would we invert the dependency?

Applying DIP: Inversion of Dependency

Generally, when we start learning OOP, we use concrete classes as the dependent classes when a class depends on them. But we have seen dealing with a concrete class as a dependent class has produced some problems. We need to find out a way so that the SocialPoster class becomes loosely coupled and does NOT depend on any concrete class. How? We can do that inverting dependency!

To invert the dependency, we have to make sure our high-level classes use abstractions. What are abstractions? Abstractions are concepts (not details) or contracts or rules or laws. The abstractions tell what-to-do. But, keep in mind, they do NOT tell you how-to-do. That is why they are called abstractions.

Let us find out why the high-level class SocialPoster needs a dependency on a low-level class the Twitter class. There could have many reasons for using the Twitter class. But, in our case, the main reason is to post on that social media.

Let us abstract posting on social media. We can invert the dependency using either an abstract interface or an abstract class. That means we will pass an abstract interface to the constructor of the client class SocialPoster instead of a concrete class. Thus depending on an abstraction, not a concrete class, the SocialPoster class inverts managing its dependency tradition. So make a contract/rule/law.

// TypeScript

interface SocialPosterInterface {
/**
* Posts on social media
*
* @param {PostData} data
* @return {void}
*/
post(data: PostData): void;
}
// PHP

interface SocialPosterInterface
{
/**
* Posts on social media
*
* @param array data
* @return void
*/
public function post(array $data): void;
}

This is our abstraction for posting on social media. The abstraction provides such a law that dictates what you have to abide by. In the code above, the abstraction, the contract dictates it has a post() method that must be used to post on social media (instead of using a method from a concrete class).

Now we can make the SocialPoster class depend on the abstraction — the SocialPosterInterface.

// TypeScript

class SocialPoster {
private socialMedia: SocialPosterInterface;

constructor(socialMedia: SocialPosterInterface) {
this.socialMedia = socialMedia;
}

post(data: PostData): void {
this.socialMedia.post(data);
}
}
// PHP

class SocialPoster
{
private SocialPosterInterface $socialMedia;

public function __construct(SocialPosterInterface $socialMedia)
{
$this->socialMedia = $socialMedia;
}

public function post(array $data): void
{
$this->socialMedia->post($data);
}
}

As you can see the SocialPoster class manages its dependency based on abstraction, so there is no need to depend on the low-level classes. No matter how the post() method of SocialPosterInterface is implemented. It just has the knowledge about the contract/rule/law but not about the implementation details. The SocialPoster class is no more coupled with a limited API. It has now unlimited power. Because, it can receive any kind of social media API object as an argument, even if the one has not been invented yet. So it is tremendously scalable and reusable.

The $socialMedia parameter of the SocialPoster constructor method has become polymorphic because of using abstraction. You can use any kind of object as an argument that implements the SocialPosterInterface.

Low-level Modules/Classes

The SocialPoster class is now decoupled in the sense that if you change in the low-level class (for example, Twitter class) does not affect the SocialPoster class anymore. Moreover, it can be reused in any context where low-level modules conform to the SocialPosterInterface abstract interface.

Remember abstraction provides one or more concepts that tell you what-to-do but not how-to-do. The how-to-do will be implemented by the low-level classes. So whatever we need to change in the low-level classes will not affect the high-level SocialPoster class unless the low-level classes break the rule provided by that abstract interface.

As we have abstracted posting on social media we can now create as many low-level classes as we need conforming to SocialPosterInterface abstract interface.

// TypeScript

class Twitter implements SocialPosterInterface {
post(data: PostData): void {
console.log('Posting on twitter...');
console.log(data.message);
}
}

class Facebook implements SocialPosterInterface {
post(data: PostData): void {
console.log('Posting on facebook...');
console.log(data.message);
}
}
// PHP

class Twitter implements SocialPosterInterface
{
public function post(array $data): void
{
var_dump('Posting on twitter...');
var_dump($data['message']);
}
}

class Facebook implements SocialPosterInterface
{
public function post(array $data): void
{
var_dump('Posting on facebook...');
var_dump($data['message']);
}
}

Now the abstraction is not depending on details, rather, details are depending on the abstraction. The SocialPosterInterface (the abstraction) does not depend on the Twitter or the Facebook class (the concrete classes, therefore, implementation details). And the Twitter and the Facebook low-level classes depend on the SocialPosterInterface which is an abstraction.

You can create more low-level classes. Just abide by the contract. It will fit in the high-level SocialPoster class. You can now see the power of DIP.

Inversion of Ownership

Have you noticed anything? Something has happened after implementing the DIP. And that is an inversion of ownership. Ownership of what? Ownership of the interface. Who owns the interface?

After applying the DIP, it seems that the SocialPoster owns the ownership of the SocialPosterInterface. But we have seen the Twitter and the Facebook classes both use the SocialPosterInterface. The ownership of the SocialPosterInterface has been ambiguous. Should we put it in the high-level module or the low-level module? Does SocialPoster own it? Or does the Twitter class or the Facebook class own it?

But why is this important? See what Uncle Bob says:

Interface needs to stand alone without belonging to either group.

Because the interface can be used by lots of different clients and implemented by lots of different modules. That is why we need to take care of the inversion of the interface ownership.

The DIP has made it happen the inversion of interface ownership. So this is a problem. We can solve it by providing a very generic name to the interface so that does not imply the use of high-level and low-level modules together.

If we change the name of SocialPosterInterface to PostInterface or Poster or Postable then it may be kept in a separate module. It may be used later by different modules. The Postable abstract interface is NOT now limited to being used for social postings. It may be used for blog postings, forum postings, and maybe for others.

// TypeScript

interface Postable {
/**
* Publishes post
*
* @param {PostData} data
* @return {unknown}
*/
post(data: PostData): unknown;
}
// PHP

interface Postable
{
/**
* Publishes post
*
* @param array<string, mixed> data
* @return mixed
*/
public function post(array $data): mixed;
}

So our high-level class SocialPoster should now look like the below.

// TypeScript

class SocialPoster {
private socialMedia: Postable;

constructor(socialMedia: Postable) {
this.socialMedia = socialMedia;
}

post(data: PostData): void {
this.socialMedia.post(data);
}
}
// PHP

class SocialPoster
{
private Postable $socialMedia;

public function __construct(Postable $socialMedia)
{
$this->socialMedia = $socialMedia;
}

public function post(array $data): void
{
$this->socialMedia->post($data);
}
}

Summary

The DIP is one of the most powerful OO design principles. It produces reusable code. It reduces tight-coupling and code duplication. It helps you write the least code. Applying the DIP makes code testable and scalable. The DIP allows you to write code that can be compatible with the code that will be written in the future.

Learning DIP, you would be able to write robust code and easily deal with more design principles such as Design by Contract (DBC), Do Not Repeat Yourself (DRY), Open-Closed Principle (OCP), Loose Coupling or Decoupling, and maybe more. And you will learn how to write polymorphic code.

See full code here: TypeScript and PHP.

Thanks for reading

--

--

Abu Jobaer

Advocate Clean Code, SOLID Principles, Software Design Patterns & Principles, and TDD.