Blog Datasheets Home About me Clients My work Services Contact

G2Labs Grzegorz Grzęda

Practical examples of applying SOLID principles to real-world scenarios

May 9, 2023

Practical Examples of Applying SOLID Principles to Real-World Scenarios

In the world of object-oriented programming, the SOLID principles are a set of guidelines for writing clean, maintainable, and extensible code. These principles, coined by Robert C. Martin, are designed to help developers create software that is easy to understand, flexible, and robust. In this blog post, we will explore practical examples of applying each of the SOLID principles – Single Responsibility Principle, Open/Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle – to real-world scenarios using C and Python.

Single Responsibility Principle (SRP)

The Single Responsibility Principle states that a class should have only one reason to change, meaning that it should have only one responsibility. Let’s consider an example of a class handling file operations in C:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

// A class for handling file operations
typedef struct {
  char* filename;
} FileHandler;

void openFile(FileHandler* handler) {
  // implementation for opening a file
}

void readFile(FileHandler* handler) {
  // implementation for reading a file
}

void closeFile(FileHandler* handler) {
  // implementation for closing a file
}

In this example, the FileHandler class has a single responsibility of handling file operations such as opening, reading, and closing files.

In Python, we can apply the SRP principle to a class representing a user object:

1
2
3
4
5
6
7
class User:
    def __init__(self, username, email):
        self.username = username
        self.email = email

    def displayUserInfo(self):
        # implementation for displaying user information

The User class is responsible for storing user information and displaying it, adhering to the Single Responsibility Principle.

Open/Closed Principle (OCP)

The Open/Closed Principle states that a class should be open for extension but closed for modification. Let’s consider an example of a shape drawing application in Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Shape:
    def draw(self):
        pass

class Circle(Shape):
    def draw(self):
        # implementation for drawing a circle

class Square(Shape):
    def draw(self):
        # implementation for drawing a square

In this example, the Shape class is open for extension by allowing new shapes to be added without modifying existing code.

Liskov Substitution Principle (LSP)

The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. Let’s consider an example of a backend service interface in C:

1
2
3
4
5
6
7
typedef struct {
  char* data;
} BackendResponse;

typedef struct {
  BackendResponse (*fetchData)();
} BackendService;

In this example, the BackendService interface can be substituted with new implementations without affecting the client code.

In Python, we can apply the LSP to a class representing different types of birds:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Bird:
    def fly(self):
        pass

class Sparrow(Bird):
    def fly(self):
        # implementation for flying like a sparrow

class Ostrich(Bird):
    def fly(self):
        raise NotImplementedError("Ostrich cannot fly")

The Ostrich subclass does not implement the fly method, adhering to the Liskov Substitution Principle.

Interface Segregation Principle (ISP)

The Interface Segregation Principle states that a client should not be forced to depend on interfaces they do not use. Let’s consider an example of a printer interface in C:

1
2
3
4
typedef struct {
  void (*print)();
  void (*scan)();
} Printer;

In this example, the Printer interface violates the ISP as the client may not need both printing and scanning capabilities.

In Python, we can apply the ISP to a class representing a management system interface:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from abc import ABC, abstractmethod

class ManagementSystem(ABC):
    @abstractmethod
    def addEmployee(self):
        pass

    @abstractmethod
    def removeEmployee(self):
        pass

The ManagementSystem interface segregates the methods based on their usage, adhering to the Interface Segregation Principle.

Dependency Inversion Principle (DIP)

The Dependency Inversion Principle states that high-level modules should not depend on low-level modules, but both should depend on abstractions. In C, we can apply the DIP to a logger module:

1
2
3
typedef struct {
  void (*log)(char* message);
} Logger;

In this example, the Logger abstraction allows high-level modules to depend on it without being tightly coupled to specific implementations.

In Python, we can apply the DIP to a class representing a message sender:

1
2
3
4
5
6
from abc import ABC, abstractmethod

class MessageSender(ABC):
    @abstractmethod
    def sendMessage(self, message):
        pass

The MessageSender abstraction allows high-level modules to depend on it without being concerned with the specific message sending implementation.

Conclusion

In this blog post, we have explored practical examples of applying the SOLID principles to real-world scenarios using C and Python. By following these principles, developers can create code that is more modular, extensible, and maintainable, leading to better software design and development. As software systems become more complex, understanding and applying the SOLID principles becomes increasingly important for writing high-quality, reliable, and scalable applications.

I hope you found this blog post helpful in understanding the practical application of SOLID principles in real-world programming scenarios! Let me know if you have any questions or feedback. Happy coding!


➡️ Composite design pattern


⬅️ Virtual destructor in C++


Go back to Posts.