Today, I’ll discuss principle #5 of SOLID principles to help write clean code in Python.
If you missed the other principles, here are the first , second , third , and the fourth one.
Let’s dive into the fifth design principle; Dependency Inversion Principle (or DIP) (the ‘D’ in SOLID).
Note: This series of articles are to demonstrate applying the SOLID principles in Python from a learning perspective. This does not mean that you should follow this approach in production code because Python has simpler capabilities that could be used. I’ll try to extrapolate on that later. For now, I’ll just show you how to apply the principles in Python.
This tutorial is about the 5th design principle in a real example and shown in a UML diagram for better illustration.
So what is DIP?
Usually, you instantiate the dependencies of a class inside the class itself. The class becomes tightly coupled with its dependencies. So if you want to change the dependency, that class would need to be /changed as well. To loosen this coupling, we supply the dependencies (to a class) from the external world. That’s where we can see the Dependency Inversion Principle in action.
Definition: The DIP can be stated as follows:
A. High-level classes should not depend on low-level classes. Both should depend on abstractions. B. Abstractions should not depend on details. Details should depend on abstractions.
It consists of two parts. The first part talks about the nature of dependencies between high-level and low-level classes. What are a high-level class and a low-level class in the first place?
A high-level class is a class that does something significant in the application while a low-level class is a class that does something auxiliary.
Example: Authentication and Membership System
Let’s take an example. Suppose you are building an authentication and membership system for a web application that needs to manage users. As a part of user management, a way of changing the password is required. When a user wants to change the password, a notification is to be sent to the user’s email about the change. In this case, the class doing the user management is the high-level class, and the class sending notifications is a low-level class.
The first part of the DIP says that high-level classes should not depend on low-level classes and both of them should depend on abstractions. Usually, a high-level class makes use of a low-level class by creating one or more instances of it within itself. Consider the classes shown below:
<img src=“https://drive.google.com/uc?export=view&id=1IUFr50W7dcvAjNu5-QlI6GSeUNHY5Bnu”,alt=“UserManager class with a change_password() method. This class depends on EmailNotifier which is a class with a notify() method.",width=“50%">
High-level class depends on a low-level class (Designed by Plantuml)
In the UML diagram above, a dotted line joining
EmailNotifier with an arrowhead pointing toward
UserManager is dependent on
As shown in the figure, there is a high-level class,
that contains the
change_password() method. The
class depends on the
EmailNotifier class for sending email
notifications to the user. In this case,
UserManager creates an
EmailNotifier , as shown in the following pseudo-code:
def change_password(username: str, oldpwd: str, newpwd: str): pass notifier = EmailNotifier() # change password here notifier.notify("Password was changed on " + DateTime.Now)
As you can see, the
change_password() method instantiates
EmailNotifier and then calls its
notify() method to send an email
What’s the problem with this design? After all, we have been using this
style of coding for a long time. The problem here is that
UserManager has too much dependency on
UserManager might need some
correction or adjustment. Further,
EmailNotifier must be made
available at the time of writing and testing
UserManager . So, you
are forced to finish writing low-level classes before you code
Additionally, future alterations to the notification system may require
UserManager class. For example, instead of email
notification, you may decide to provide SMS notifications, in which case
you must change the code of
UserManager to replace
with the new notification class.
<img src=“https://drive.google.com/uc?export=view&id=1N5JbzkSE6cHgr5V55BoqNkUiJ0Sseyvv”,alt=“One interface: INotifier with notify() method, and UserManager class with change_password() method and a notifier in the constructor. Three classes are inheriting from INotifier interface.",width=“50%">
Design conforming to DIP. (Designed by Plantuml)
UserManager class no longer uses
Instead, an interface;
INotifier , has been introduced. The
INotifier interface is implemented by the
EmailNotifier class. The
UserManager receives an instance of a class that
INotifier from the external world.
change_password() method then uses this instance to call the
notify() method. If you decide to switch from
PopupNotifier , this decision won’t have any impact
UserManager class, as you are supplying the dependency from
outside. Thus, the direction of dependencies is reversed after applying
The second part of DIP tells us that abstractions should not depend on
details; rather, details should depend on abstractions. This means that
you should design the
INotifier interface (abstraction) by looking
at the needs of the
UserManager class. The
should not be designed while looking at the needs of the
EmailNotifier class (details).
The Dependency Inversion Principle is applied when you have a class that
has a dependency on another class. In our example, the
class had a dependency on the
EmailNotify class. To solve that, we
introduced an interface
INotifier and instantiated an instance of it
UserManager class constructor. By looking at the needs of the
UserManager class, we designed the
INotifier interface which was
SMSNotifier , and
By doing so, we have made the high-level classes depend on low-level classes.
- Beginning SOLID Principles and Design Patterns for ASP.NET Developers by Bipin Joshi