Thought it might be a bit of fun to jot down a quick primer for SOLID principles - helpful if you have an exam and need to crunch.

What is SOLID?

  • SOLID is a set of five principles for designing clean and maintainable software, primarily applied to object-oriented programming.
  • These principles were introduced by Robert C. Martin and Michael Feathers as guidelines to promote good design practices.

Single Responsibility Principle (SRP)

  • Definition:
    • A class should have only one reason to change, meaning it should have a single responsibility or job.
  • Importance:
    • SRP promotes code cohesion and maintainability.
    • Each class focuses on one aspect of the application, making it easier to understand and modify.
  • Example:
    • A Customer class should handle customer-related operations, not mixing them with order processing.

Open-Closed Principle (OCP)

  • Definition:
    • Software entities (e.g., classes, modules) should be open for extension but closed for modification.
  • Importance:
    • OCP allows you to add new features or behaviors to the system without altering existing code.
    • Promotes code reusability and minimizes the risk of introducing new bugs.
  • Example:
    • Achieved by using interfaces, abstract classes, and inheritance for extensibility.

Liskov Substitution Principle (LSP)

  • Definition:
    • Subtypes must be substitutable for their base types without altering the correctness of the program.
  • Importance:
    • LSP ensures that derived classes adhere to the contract defined by their base classes.
    • Violations can lead to unexpected behaviour and bugs.
  • Example:
    • If Square is a subtype of Rectangle, it should have the same behavior (getters and setters for width and height).

Interface Segregation Principle (ISP)

  • Definition:
    • Clients should not be forced to depend on interfaces they do not use.
  • Importance:
    • ISP avoids large, monolithic interfaces that force implementing unnecessary methods.
    • Promotes more focused and cohesive interfaces.
  • Example:
    • Splitting a large IWorker interface into IEater and ISleeper for classes with different behaviours.

Dependency Inversion Principle (DIP)

  • Definition:
    • 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.
  • Importance:
    • DIP promotes loose coupling between classes and modules.
    • It allows for easy substitution of components and facilitates unit testing.
  • Example:
    • Instead of directly depending on a specific database, a higher-level class depends on an abstract data access interface, which can be implemented for various databases.

Benefits of SOLID Principles

  • Enhanced maintainability, readability, and scalability of code.
  • Reduced coupling and improved testability.
  • Encourages the use of design patterns that promote good design practices.
  • Supports the development of flexible and extensible software.

Challenges

  • Initial implementation effort may be slightly higher.
  • Strict adherence to SOLID principles might not always be suitable for all scenarios.

Summary

SOLID principles provide a foundation for designing clean, maintainable, and flexible software in object-oriented programming.

They guide developers in creating code that is less error-prone and easier to extend and modify.

Code Example

Single Responsibility Principle

The Single Responsibility Principle in object-oriented programming states that a class should have only one reason to change. In other words, a class should have a single, well-defined responsibility. Here’s an example in C# that demonstrates a violation of the Single Responsibility Principle:

using System;

// Violates the Single Responsibility Principle
public class User
{
    public string Name { get; set; }
    public void SaveToDatabase()
    {
        // Code for saving the user to a database
        Console.WriteLine("User saved to the database.");
    }
    public void SendEmail()
    {
        // Code for sending a welcome email to the user
        Console.WriteLine("Welcome email sent to the user.");
    }
}

In this example, the User class has two responsibilities: saving the user to a database and sending a welcome email. This violates the SRP because a change in one of these responsibilities may affect the other. For instance, if you need to change the way users are saved to the database, you’d have to modify the User class, potentially impacting the email-sending functionality.

To adhere to the SRP, you should split these responsibilities into separate classes:

using System;

// Following the Single Responsibility Principle
public class User
{
    public string Name { get; set; }
}

public class UserRepository
{
    public void SaveToDatabase(User user)
    {
        // Code for saving the user to a database
        Console.WriteLine("User saved to the database.");
    }
}

public class EmailService
{
    public void SendWelcomeEmail(User user)
    {
        // Code for sending a welcome email to the user
        Console.WriteLine("Welcome email sent to the user.");
    }
}

In this revised code, we have three separate classes, each with a single responsibility. The User class only represents user data, the UserRepository class handles database operations, and the EmailService class takes care of sending emails. This adheres to the Single Responsibility Principle, making the code more maintainable and less prone to unintended side effects when changes are required in one part of the system.