avatarHaseeb Kamal

Summary

The provided content discusses the Dependency Inversion Principle (DIP) in the context of Python programming, emphasizing its role in achieving loose coupling and better code design.

Abstract

The article is the fifth installment in a series exploring the SOLID principles of software design, with a focus on the Dependency Inversion Principle (DIP). It defines DIP as a design pattern that requires high-level modules to depend on abstractions rather than low-level modules, thereby inverting the traditional dependency relationship. The author illustrates the concept with a Python code example involving a robot that consumes different types of food, demonstrating how DIP can prevent tight coupling and strong dependencies, making the codebase more maintainable and adaptable to change. The solution proposed involves introducing an Eatable interface to abstract the details of food items, allowing the Robot class to interact with any food type that implements this interface without the need for conditional branching or refactoring due to changes in the food classes. The article concludes by summarizing the benefits of DIP, such as enforcing loose coupling and enabling the reuse of higher-level components, and expresses the hope that readers have found value in the series on SOLID principles.

Opinions

  • The author advocates for the use of DIP to create a more robust and maintainable codebase in Python.
  • The article suggests that traditional software architecture, where high-level components depend directly on low-level components, leads to tight coupling and should be avoided.
  • The author emphasizes the importance of abstraction layers, such as interfaces, in reducing dependencies between different levels of software components.
  • The author implies that adhering to SOLID principles, including DIP, is crucial for writing better Python code that can easily accommodate future changes and improvements.
  • The use of if-else branching to handle different subclasses is presented as a suboptimal design choice that can be eliminated with proper application of DIP.
  • The article expresses confidence in the value of the SOLID principles series for Python programmers looking to improve their code design skills.

A Guide to Loose Coupling and Writing Better Python Code With Dependency Inversion

Dive into the popular design pattern

Photo by Rachel Nickerson / Unsplash

This post is part 5 of a series on the SOLID principles. You can find post 4 here, post 3 here, post 2 here and post 1 here.

We have finally reached the last of the SOLID principles. As usual, we start with a definition:

Principle 5 is named the dependency inversion principle. The definition has two parts:

A. High-level modules should not import anything from low-level modules. Both should depend on abstractions (e.g., interfaces).

B. Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

Source: Dependency inversion principle — Wikipedia

In traditional software architecture we design lower level components to be used/consumed by higher level components. In other words, higher level components depend on lower level components. This dependency causes tight coupling in the software. As explained in principle one, we strive to achieve loose coupling to make it easier to develop, maintain and change code in the future.

The dependency inversion principle inverts this dependency in the sense that instead of higher level components depending on lower level ones, both should depend on abstractions. This abstraction layer would be an intermediate component that sits between the higher and lower level components. The two will then use this component to communicate and interact amongst each other. The abstraction component would usually be implemented as an interface.

Time for some code and a concrete example!

Code

Consider the following example where we model a robot:

The Robot class has only one function get_energy. We model an Apple that the robot can get energy from.

Now, at some point the robot will get tired of eating apples. So we add a Chocolate class to offer more eating options to the robot.

Notice however, what happens to the get_energy method. We must pass a string as a parameter indicating what eatable the robot should eat. Also, we must use if-else branching on the two different eatables. Now you can imagine if more eatables are added we would need more else branches. This is not good design.

Furthermore, because Robot has a dependency on each of the individual eatables we will run into issues if the implementation of one of the eatables changes. For example, if we introduce an additional parameter to the eat method in Chocolate the code in get_energy would break and we would have to refactor it to reflect the changes in Chocolate. These issues occur due to tight coupling and strong dependencies and are a violation of the dependency inversion principle.

Currently our architecture looks as follows:

Robot with dependencies on Apple and Chocolate

Solution

To solve this, we need to introduce an abstraction layer. We modify the architecture as follows:

The code looks like so:

We create an Eatable interface that is implemented by both Apple and Chocolate. We change the method signature of get_energy so that it expects an argument of type Eatable instead of str. This means we can get rid of the if-else branching. Furthermore, since all eatables implement the Eatable interface we are sure that there will be no code breakage if there changes to Chocolate or Apple.

Conclusion

In this post, we looked at the dependency inversion principle. The principle essentially states that higher level modules should not depend on lower level modules. Instead both should depend on abstractions. We make higher level modules independent of implementation specifics in lower level modules.

The principle:

  • Enforce loose coupling and thereby helps make code more robust in face of changes.
  • Allows re-use of higher-level components since the abstraction layer prevents code breakage in case the lower-level components need to be changed.

We have finally reached the end!

I hope you enjoyed this post and all the previous posts on the SOLID principles.

Originally published at https://haseebkamal.com

Programming
Solid
Design Patterns
Software Engineering
Python
Recommended from ReadMedium