DEV Community

Caique Santos
Caique Santos

Posted on

SOLID Principles you won't forget again

Introduction

SOLID is a set of design principles for writing clean, maintainable, and scalable code. It stands for:

S- Single Responsibility Principle (SRP)

A class or function should have only one responsibility. In the bad example below, the CreateUser function should not handle email-related logic.

Bad example:

public class UserService {
    public void CreateUser(string email) {

        var user = new User(email); 
        _repository.Save(user);

        var smtpClient = new SmtpClient("smtp.company.com"); 
        var message = new MailMessage(                       
            "no-reply@myapp.com", email,"Welcome!",          
            "Your account was created successfully."         
        );                                                   
       smtpClient.Send(message);                           
    }
}
Enter fullscreen mode Exit fullscreen mode

Good example:

public class UserService{
     public void CreateUser(string email) {

        var user = new User(email); 
        _repository.Save(user);

        _emailService.SendWelcome(email);
    }
}
Enter fullscreen mode Exit fullscreen mode

O β€” Open/Closed Principle (OCP)

Code should be open for extension but closed for modification. This means you should be able to add new behavior without changing existing code. In the bad example below, whenever a new discount is added, you edit the function.

Bad example:

public class DiscountService {
    public double Calculate(string type) {
        if (type == "VIP") return 0.2;            
        if (type == "Regular") return 0.1;
        if (type == "Employee") return 0.3;

        return 0;
    }
}
Enter fullscreen mode Exit fullscreen mode

Good example:

public interface IDiscount {
    double Calculate();
}
public class VipDiscount : IDiscount{ 
    public double Calculate() => 0.2;
}
public class RegularDiscount : IDiscount {
    public double Calculate() => 0.1;
}

public class DiscountService {
    public double Calculate(IDiscount discount) {
        return discount.Calculate();
    }
}
Enter fullscreen mode Exit fullscreen mode

L- Liskov Substitution Principle (LSP)

Subtypes must be replaceable for their base types without breaking the system. This means you should be able to use a subclass anywhere the base class is expected, without causing issues. In the bad example below, the subclass breaks the expected behavior by throwing an exception.

Bad example:

public class Bird {
    public virtual void Fly() { }
}

public class Penguin : Bird {
    public override void Fly() {
        throw new Exception("Penguins can't fly");
    }
}

Bird bird = new Penguin();
bird.Fly();                      // throws exception

Enter fullscreen mode Exit fullscreen mode

Good example:

public abstract class Bird { }

public interface IFlyingBird {
    void Fly();
}

public class Eagle : Bird, IFlyingBird {
    public void Fly() { }
}

public class Penguin : Bird { }
Enter fullscreen mode Exit fullscreen mode

I- Interface Segregation Principle (ISP)

Clients should not depend on methods they do not use. The example below looks like the one from LSP, but the difference here it's about dependency minimization, and the other one is about behavioral contracts.

Bad example:

public interface IWorker
{
    void Work();
    void Eat();
}

public class Robot : IWorker
{
    public void Work() { }

    public void Eat()
    {
        //forced to implement this method even though it doesn't use it
    }
}

Enter fullscreen mode Exit fullscreen mode

Good example:

public interface IWorkable {
    void Work();
}

public interface IEatable {
    void Eat();
}

public class Human : IWorkable, IEatable {
    public void Work() { }
    public void Eat() { }
}

public class Robot : IWorkable {
    public void Work() { }
}
Enter fullscreen mode Exit fullscreen mode

D- Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules. Both should depend on abstractions. In other words, don’t depend on concrete implementations β€” depend on abstractions.

Bad example:

public class UserService
{
    private EmailService _emailService = new EmailService();

    public void CreateUser(string email)
    {
        // logic
        _emailService.Send(email);
    }
}
Enter fullscreen mode Exit fullscreen mode

Good example:

public interface IEmailService{
    void Send(string email);
}
public class EmailService : IEmailService{
    public void Send(string email) { }
}
public class UserService{
    private readonly IEmailService _emailService;

    public UserService(IEmailService emailService) {
        _emailService = emailService;
    }

    public void CreateUser(string email) {
        _emailService.Send(email);
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)