SOLID Principles: A Quick Guide (.NET examples)
Tags: programming, tutorial, architecture, beginners
Published: 2023-06-28 18:35:53

Recently, I was thinking about some topics for a short and knowledge-refreshing article. One subject that I rarely hear being talked about, but is really important in software development, are the SOLID principles. For a lot of experienced developers, these "rules" may seem pretty clear, yet I often come across code changes that don't follow these principles. Sometimes, I even find some that I've made myself, so no disrespect intended.

To enhance the value of this article, I've included some basic explanations and examples in C#

## Single Responsibility Principle (SRP)

Each class should have one, and only one, reason to change. This means a class should only have one job.

Example:
```csharp
public class Order
{
    public void CalculateTotalSum() { /*...*/ }
    public void GetItems() { /*...*/ }
    public void PrintOrder() { /*...*/ }
    public void ShowOrder() { /*...*/ }
}
```

This Order class violates SRP because it has more than one responsibility. A better approach is to split this into several classes, each handling a single concern.

---

## Open-Closed Principle (OCP)
Software entities should be open for extension but closed for modification. This encourages stability while allowing system growth.

Example:
```csharp
public class Rectangle
{
    public double Height { get; set; }
    public double Width { get; set; }
}
public class AreaCalculator
{
    public double TotalArea(Rectangle[] arrRectangles)
    {
        double area = 0;
        foreach (var objRectangle in arrRectangles)
        {
            area += objRectangle.Height * objRectangle.Width;
        }
        return area;
    }
}
```
In this example, if we want to add a new shape, we'd have to modify the AreaCalculator class, which violates OCP.

---

## Liskov Substitution Principle (LSP)
This principle insists that subclasses must be substitutable for their base classes without affecting functionality.

Example:

```csharp
public class Vehicle
{
    public virtual void StartEngine() { /*...*/ }
}

public class ElectricCar : Vehicle
{
    public override void StartEngine() { /*...*/ }
}

public class Bicycle : Vehicle
{
    public override void StartEngine() { /*...*/ }
}
```
Here, a Bicycle doesn't have an engine to start, so substituting a Vehicle with a Bicycle would cause issues, thus violating LSP. A better approach would be to have a separate class for vehicles with engines.

---

## Interface Segregation Principle (ISP)
No client should be forced to depend on interfaces they do not use.

Example

```csharp
public interface IWorker
{
    void Work();
    void Eat();
}
```
Here, if a robot implements IWorker, the Eat method is useless. This violates ISP. It's better to break this into two interfaces: IWorker and IEater.

---

## Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules, but both should depend on abstractions. This reduces tight coupling.

Example:

```csharp
public class Email
{
    public void SendEmail() { /*...*/ }
}
public class Notification
{
    private Email _email;
    public Notification()
    {
        _email = new Email();
    }
}
```

here, the Notification class is tightly coupled with the Email class. Using DIP, we can introduce an interface to decouple them.

---

Despite their importance in writing maintainable and scalable code, these principles are frequently overlooked. For me it was a short, simple and much needed refresher. Hopefully for any other reader too 😀
Feel free to comment your thoughts

Happy coding!