SOLID Principles in Software Development using C#

Software development is a complex craft, often likened to constructing a building. Just as architects and engineers follow principles to ensure buildings are sturdy and maintainable, software engineers adhere to principles to create robust, scalable, and maintainable code. One set of principles gaining widespread recognition in the software development community is SOLID, an acronym introduced by Robert C. Martin. This article will explore the SOLID principles and demonstrate how they can be applied in C#.

S. Single Responsibility Principle (SRP)

The Single Responsibility Principle states that a class should have only one reason to change. In other words, a class should have only one responsibility. This principle promotes high cohesion and reduces coupling, making classes easier to understand, maintain, and test.

// Example demonstrating Single Responsibility Principle
public class UserManager
{
    private readonly IUserRepository _userRepository;

    public UserManager(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public void RegisterUser(User user)
    {
        _userRepository.Add(user);
        // Additional logic for user registration
    }

    public void DeleteUser(User user)
    {
        _userRepository.Delete(user);
        // Additional logic for user deletion
    }
}

O. 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 extend the behavior of a module without modifying its source code.

// Example demonstrating Open/Closed Principle
public abstract class Shape
{
    public abstract double Area();
}

public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }

    public override double Area()
    {
        return Width * Height;
    }
}

public class Circle : Shape
{
    public double Radius { get; set; }

    public override double Area()
    {
        return Math.PI * Radius * Radius;
    }
}

L. Liskov Substitution Principle (LSP)

The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. In other words, derived classes must be substitutable for their base classes.

// Example demonstrating Liskov Substitution Principle
public class Rectangle
{
    public virtual double Width { get; set; }
    public virtual double Height { get; set; }

    public double Area()
    {
        return Width * Height;
    }
}

public class Square : Rectangle
{
    private double _side;

    public override double Width
    {
        get => _side;
        set
        {
            _side = value;
            Height = value;
        }
    }

    public override double Height
    {
        get => _side;
        set
        {
            _side = value;
            Width = value;
        }
    }
}

I. Interface Segregation Principle (ISP)

The Interface Segregation Principle states that clients should not be forced to implement an interface they don't use. This principle encourages developers to design fine-grained interfaces specific to the client's needs.

// Example demonstrating Interface Segregation Principle
public interface IWorker
{
    void Work();
}

public interface IEater
{
    void Eat();
}

public class Robot : IWorker
{
    public void Work()
    {
        // Perform work
    }
}

public class Human : IWorker, IEater
{
    public void Work()
    {
        // Perform work
    }

    public void Eat()
    {
        // Perform eating
    }
}

D. 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. Additionally, abstractions should not depend on details; rather, details should depend on abstractions.

// Example demonstrating Dependency Inversion Principle
public interface IMessageSender
{
    void SendMessage(string message);
}

public class EmailSender : IMessageSender
{
    public void SendMessage(string message)
    {
        // Send message via email
    }
}

public class SmsSender : IMessageSender
{
    public void SendMessage(string message)
    {
        // Send message via SMS
    }
}

public class NotificationService
{
    private readonly IMessageSender _messageSender;

    public NotificationService(IMessageSender messageSender)
    {
        _messageSender = messageSender;
    }

    public void SendNotification(string message)
    {
        _messageSender.SendMessage(message);
    }
}

By adhering to the SOLID principles, developers can create more maintainable, flexible, and easier-to-understand code. While these principles may require additional effort and thought during the initial development phase, they ultimately lead to better software design and reduced technical debt in the long run.


Similar Articles