Solid Principles in a nutshell

rahul sahay
6 min readJun 30, 2023

--

The SOLID principles are a set of five design principles that were introduced by Robert C. Martin (also known as Uncle Bob) as a guide for software developers to create more maintainable and flexible software systems. These principles help in designing software that is easy to understand, maintain, and extend. The SOLID acronym stands for:

  1. Single Responsibility Principle (SRP):
    This principle states that a class should have only one reason to change. In other words, a class should have a single responsibility or purpose. By keeping classes focused on a single responsibility, it becomes easier to understand, test, and maintain them.
  2. Open-Closed Principle (OCP):
    The Open-Closed Principle states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means that you should be able to add new functionality to a system without modifying existing code. By using techniques such as inheritance, interfaces, and abstraction, you can achieve this principle.
  3. 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. In other words, if you have a base class and you create a derived class, the derived class should be able to substitute the base class wherever it’s used, without causing any unexpected behavior.
  4. Interface Segregation Principle (ISP):
    The Interface Segregation Principle states that clients should not be forced to depend on interfaces they do not use. It promotes the idea of having smaller, focused interfaces rather than large, general-purpose interfaces. This helps to avoid unnecessary dependencies and ensures that clients only depend on the methods they actually need.
  5. Dependency Inversion Principle (DIP):
    The Dependency Inversion Principle states that high-level modules should not depend on low-level modules; both should depend on abstractions. It also suggests that abstractions should not depend on details; details should depend on abstractions. This principle helps in creating loosely coupled systems and promotes the use of dependency injection and inversion of control (IoC) containers.

By following these SOLID principles, developers can create software that is more modular, flexible, and easier to maintain and extend. These principles contribute to better code quality, reusability, and testability, ultimately leading to more robust and scalable software systems.

Suppose we have a simple application that manages books in a library. We’ll focus on the Book class and its related functionalities. Here's how we can apply the SOLID principles:

  1. Single Responsibility Principle (SRP):
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public int Pages { get; set; }
// ...

public void Save()
{
// Code to save the book to the database
}

public void PrintDetails()
{
Console.WriteLine($"Title: {Title}");
Console.WriteLine($"Author: {Author}");
Console.WriteLine($"Pages: {Pages}");
// ...
}
}

In this example, the Book class has two responsibilities: saving the book to the database and printing its details. To adhere to SRP, we can separate these responsibilities into different classes:

public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public int Pages { get; set; }
// ...
}

public class BookRepository
{
public void Save(Book book)
{
// Code to save the book to the database
}
}

public class BookPrinter
{
public void PrintDetails(Book book)
{
Console.WriteLine($"Title: {book.Title}");
Console.WriteLine($"Author: {book.Author}");
Console.WriteLine($"Pages: {book.Pages}");
// ...
}
}

By separating the responsibilities, the Book class becomes more focused and easier to maintain.

  1. Open-Closed Principle (OCP):

Let’s assume we want to introduce different formats for printing book details, such as plain text and JSON. We can modify the code to follow the OCP:

public abstract class BookPrinter
{
public abstract void PrintDetails(Book book);
}

public class PlainTextBookPrinter : BookPrinter
{
public override void PrintDetails(Book book)
{
Console.WriteLine($"Title: {book.Title}");
Console.WriteLine($"Author: {book.Author}");
Console.WriteLine($"Pages: {book.Pages}");
// ...
}
}

public class JsonBookPrinter : BookPrinter
{
public override void PrintDetails(Book book)
{
var json = JsonConvert.SerializeObject(book);
Console.WriteLine(json);
}
}

By introducing an abstract BookPrinter class, we can create different printer implementations without modifying existing code. The BookPrinter class is open for extension, allowing us to introduce new formats for printing book details.

  1. Liskov Substitution Principle (LSP):

The LSP states that objects of a superclass should be replaceable with objects of its subclasses. In our example, we can demonstrate this principle with different types of books, such as FictionBook and NonFictionBook:

public abstract class Book
{
public string Title { get; set; }
public string Author { get; set; }
public int Pages { get; set; }
// ...
}

public class FictionBook : Book
{
public string Genre { get; set; }
// ...
}

public class NonFictionBook : Book
{
public string Topic { get; set; }
// ...
}

With this design, we can substitute instances of the Book class with instances of its derived classes, such as FictionBook and NonFictionBook, without affecting the correctness of the program.

  1. Interface Segregation Principle (ISP):

Suppose we

want to introduce the ability to export book data to different file formats, such as PDF and CSV. We can apply ISP by introducing separate interfaces for each format:

public interface IPdfExporter
{
void ExportToPdf(Book book);
}

public interface ICsvExporter
{
void ExportToCsv(Book book);
}

public class PdfExporter : IPdfExporter
{
public void ExportToPdf(Book book)
{
// Code to export book details to PDF
}
}

public class CsvExporter : ICsvExporter
{
public void ExportToCsv(Book book)
{
// Code to export book details to CSV
}
}

By using specific interfaces for each export format, we avoid forcing clients to depend on methods they don’t need. Clients can then depend only on the necessary interfaces, such as IPdfExporter or ICsvExporter.

  1. Dependency Inversion Principle (DIP):

Let’s say we want to introduce logging when saving a book. We can apply DIP to decouple the BookRepository from a specific logging implementation:

public interface ILogger
{
void Log(string message);
}

public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"Logging: {message}");
}
}

public class BookRepository
{
private ILogger logger;

public BookRepository(ILogger logger)
{
this.logger = logger;
}

public void Save(Book book)
{
// Code to save the book to the database
logger.Log($"Book '{book.Title}' saved.");
}
}

By depending on the abstraction ILogger instead of a concrete logging implementation, the BookRepository becomes more flexible, and different logger implementations can be easily swapped.

These pragmatic examples illustrate how the SOLID principles can be applied in a .NET context to create maintainable, extensible, and flexible software systems.

📢 Attention microservices enthusiasts! Are you ready to apply the SOLID principles at the microservices level? Let’s take your knowledge to the next level and see how these principles can be implemented in real-world scenarios. 🔧🚀

1️⃣ “Creating .NET Core Microservices using Clean Architecture” 🏢
Master the art of building scalable microservices with clean architecture principles. 👷‍♂️
Link: Creating .NET Core Microservices using Clean Architecture

2️⃣ “Securing Microservices using Identity Server 4” 🔒
Learn how to implement robust security measures for your microservices using Identity Server 4. 🛡️
Link: Securing Microservices using Identity Server 4

3️⃣ “Implementing Cross-Cutting Concerns for ASP.NET Microservices” ⚙️
Discover techniques for handling cross-cutting concerns in your ASP.NET microservices architecture. 🔄
Link: Implementing Cross-Cutting Concerns for ASP.NET Microservices

4️⃣ “Versioning Microservices” 🆙
Master the art of versioning your microservices effectively to ensure seamless updates and maintenance. 🔄
Link: Versioning Microservices

5️⃣ “Building E-commerce Angular Application” 🛍️
Learn to build a powerful e-commerce application using Angular for a seamless shopping experience. 💻🛒
Link: Building E-commerce Angular Application

6️⃣ “Deploying Microservices to Kubernetes and AKS” ☁️
Discover the world of Kubernetes and AKS to effectively deploy your microservices to the cloud. ☁️
Link: Deploying Microservices to Kubernetes and AKS

7️⃣ “Docker for .NET and Angular Developers” 🐳
Learn the fundamentals of Docker and its usage in .NET and Angular development environments. 🐳
Link: Docker for .NET and Angular Developers

🔥 Don’t miss out on this opportunity to level up your microservices skills! Enroll now and unlock the secrets of modern application development! 💪

Thanks for joining me.

Happy Coding

Originally published at My View.

--

--

rahul sahay
rahul sahay

Written by rahul sahay

🌟 Unleashing the Power of Languages! 🚀 Expert polyglot developer, author, and Course creator on a mission to transform coding into an art. 🎨 Join me in

No responses yet