π Hey there, fellow software enthusiasts!
I'm Revisto, a passionate software engineer π¨βπ», and I want to dive into the exciting world of SOLID principles.
SOLID is a set of rules and principles that help us create maintainable, reusable, and flexible software designs. These principles guide us in building software that can adapt and grow as our projects evolve. Today, we will focus on the third principle of SOLID.
- S: Single Responsibility Principle
- O: Open-Closed Principle
- L: Liskov Substitution Principle
- I: Interface Segregation Principle
- D: Dependency Inversion Principle
The Dependency Inversion Principle (DIP):
β¨ Now, let's shine a light on the Dependency Inversion Principle (DIP). This principle focuses on managing dependencies between classes, aiming to create software systems that are flexible, maintainable, and extensible.
π‘ The core idea of the DIP can be summarized as "depend on abstractions, not on concretions." In other words, classes should rely on interfaces or abstract classes rather than concrete implementations. By doing so, we invert the traditional dependency flow.
Robert C. Martin, one of the proponents of SOLID principles, defines the Dependency Inversion Principle in two parts:
High-level modules should not depend on low-level modules. Both should depend on abstractions. This means that the modules at a higher level of abstraction should not rely on the lower-level details. Instead, they should depend on abstract interfaces or classes.
Abstractions should not depend on details. Details should depend on abstractions. This emphasizes that the abstract interface or class should not be influenced by the specific implementation details. It should provide a contract that the concrete implementations adhere to.
π Common Example:
Understanding the Scenario:
Imagine we have a Logger class that is responsible for writing log messages to a file. We also have a Calculator class that performs arithmetic operations and uses the Logger class to log information about those operations.
class Logger:
def log(self, message):
with open('log.txt', 'a') as f:
f.write(message + '\n')
class Calculator:
def __init__(self):
self.logger = Logger()
def add(self, x, y):
result = x + y
self.logger.log(f"Added {x} and {y}, result = {result}")
return result
β In the original implementation, the Calculator class directly creates an instance of the Logger class within its constructor. This creates a strong dependency between the two classes, making it difficult to modify or replace the Logger implementation in the future.
from abc import ABC, abstractmethod
class LoggerInterface(ABC):
@abstractmethod
def log(self, message):
pass
class Logger(LoggerInterface):
def log(self, message):
with open('log.txt', 'a') as f:
f.write(message + '\n')
class Calculator:
def __init__(self, logger: LoggerInterface):
self.logger = logger
def add(self, x, y):
result = x + y
self.logger.log(f"Added {x} and {y}, result = {result}")
return result
To adhere to the Dependency Inversion Principle, we can introduce an interface, LoggerInterface, which defines the log method. The Logger class then implements this interface, ensuring it adheres to the contract defined by the interface.
By adhering to the Dependency Inversion Principle, we achieve loose coupling between classes, making our software more flexible and easier to maintain. It also enhances code reusability and allows for more straightforward testing and future modifications.
π Stay tuned for more articles where we'll explore other exciting programming topics and delve into the exciting world of software engineering.
Thanks for reading. Feel free to comment your thoughtsπ. Hope this post was helpful. You can hit me up on Linked In and Github.
Happy coding!
Top comments (0)