Blog.

SOLID Programming principles

Cover Image for SOLID Programming principles
Michael
Michael

Understanding the SOLID Programming Principles: A Beginner's Guide

As software developers, we constantly strive to write code that is not only functional but also maintainable, scalable, and adaptable to change. One set of principles that aids in achieving these goals is the SOLID principles. Coined by Robert C. Martin (also known as Uncle Bob), these principles serve as guidelines for writing clean, understandable, and robust code. In this article, we'll delve into each of the SOLID principles - Single Responsibility Principle (SRP), Open/Closed Principle (OCP), Liskov Substitution Principle (LSP), Interface Segregation Principle (ISP), and Dependency Inversion Principle (DIP). For each principle, we'll discuss what it entails and provide examples of both adhering to and violating the principle to better understand its significance.

1. Single Responsibility Principle (SRP):

The SRP states that a class should have only one reason to change, meaning it should have only one responsibility or job. Let's consider an example of a User class

Good Practice: The User class should be responsible for managing user data, such as storing user details and performing user-related operations like authentication and authorization.

Bad Practice: Mixing user data management with unrelated functionalities like sending emails or generating reports violates the SRP. It's better to have separate classes for these tasks.

Good Practice:

class User:
    # User data management methods here
    ...

Bad Practice:

class User:
    # User data management methods mixed with unrelated functionality
    ...
    def send_email(self):
        # Send email functionality here
        ...
    def generate_report(self):
        # Generate report functionality here
        ...

2. Open/Closed Principle (OCP):

The OCP states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.

  • Good Practice: Using inheritance or interfaces to allow for adding new functionality without modifying existing code. For example, defining an interface Shape and implementing it with classes like Circle and Square.

  • Bad Practice: Modifying existing code directly instead of extending it. For instance, adding new logic directly to an existing class instead of creating a new subclass.

Good Practice:

interface Shape:
    method draw()

class Circle implements Shape:
    method draw():
        # Circle drawing logic here
        ...

class Square implements Shape:
    method draw():
        # Square drawing logic here
        ...

Bad Practice:

class Shape:
    method draw():
        # Drawing logic here
        ...

class Circle:
    method draw():
        # Circle drawing logic here, modifying existing code
        ...

class Square:
    method draw():
        # Square drawing logic here, modifying existing code
        ...

3. Liskov Substitution Principle (LSP):

The LSP states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program.

  • Good Practice: Subclassing should not alter the behavior of the superclass. For example, a Square should be substitutable for a Rectangle without breaking the expected behavior.

  • Bad Practice: Modifying behavior in subclasses in a way that violates the contract established by the superclass. For instance, overriding methods in a way that changes their expected behavior.

Good Practice:

class Rectangle:
    method area():
        # Calculate area logic here
        ...

class Square extends Rectangle:
    method set_width():
        # Set width logic here
        ...
    method set_height():
        # Set height logic here
        ...

Bad Practice:

class Rectangle:
    method area():
        # Calculate area logic here
        ...

class Square extends Rectangle:
    method set_side():
        # Set side logic here, violating LSP as it changes expected behavior
        ...

4. Interface Segregation Principle (ISP):

The ISP states that clients should not be forced to depend on interfaces they do not use.

  • Good Practice: Breaking down large interfaces into smaller, more specific ones to ensure that clients only implement what they need. For example, breaking an Employee interface into Workable and Feedable for different types of employees.

  • Bad Practice: Creating monolithic interfaces that force clients to implement methods they don't need, leading to unnecessary dependencies.

Good Practice:

interface Workable:
    method work()

interface Feedable:
    method eat()

class Employee implements Workable, Feedable:
    method work():
        # Work logic here
        ...
    method eat():
        # Eat logic here
        ...

Bad Practice:

interface Employee:
    method work()
    method eat()
    method sleep()  # Unnecessary method for all employees

5. Dependency Inversion Principle (DIP):

The DIP states that high-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions.

  • Good Practice: Designing code to depend on abstractions (interfaces or abstract classes) rather than concrete implementations. For example, injecting dependencies through constructor parameters instead of instantiating them directly.

  • Bad Practice: Directly instantiating dependencies within a class, tightly coupling it to specific implementations and making it harder to change or extend.

Good Practice:

class Logger:
    method log(message)

class UserService:
    method __init__(logger: Logger):
        self.logger = logger

Bad Practice:

class UserService:
    method __init__():
        self.logger = Logger()  # Directly instantiating Logger, violating DIP

In conclusion, understanding and applying the SOLID principles can significantly improve the quality and maintainability of our code. By following these guidelines, we can write software that is easier to understand, extend, and maintain, ultimately benefiting both developers and users alike.