# SOLID Principles: The Foundation of Clean Code

---

## Introduction

When I started my software development journey, I wrote code that "worked" but was painful to maintain. Every new feature felt like defusing a bomb—one wrong move and the entire application could break. Then I discovered SOLID principles, and everything changed.

SOLID isn't just an acronym; it's a mindset shift that transforms how you design classes and systems. In this article, I'll break down each principle with practical C# examples that you can apply immediately.

## The Problem: Why Bad Code Happens

Before diving into SOLID, let's understand what happens without it:

```csharp
public class UserService
{
    public void RegisterUser(string email, string password)
    {
        // Validate email
        if (!email.Contains("@")) throw new Exception("Invalid email");
        
        // Hash password
        var hashedPassword = BCrypt.HashPassword(password);
        
        // Save to database
        using var conn = new SqlConnection("connection_string");
        conn.Open();
        var cmd = new SqlCommand("INSERT INTO Users...", conn);
        cmd.ExecuteNonQuery();
        
        // Send welcome email
        var smtp = new SmtpClient("smtp.gmail.com");
        smtp.Send(new MailMessage("noreply@app.com", email, "Welcome!", "..."));
        
        // Log activity
        File.AppendAllText("log.txt", $"User {email} registered at {DateTime.Now}");
    }
}
```

**Problems:**

* Hard to test (database, email, file system dependencies)
    
* Hard to maintain (one method does everything)
    
* Hard to extend (want to add SMS notifications? Modify this method)
    
* Hard to reuse (validation logic is trapped inside)
    

SOLID principles solve these exact problems.

---

## Understanding SOLID

**SOLID** stands for:

1. **S**ingle Responsibility Principle (SRP)
    
2. **O**pen/Closed Principle (OCP)
    
3. **L**iskov Substitution Principle (LSP)
    
4. **I**nterface Segregation Principle (ISP)
    
5. **D**ependency Inversion Principle (DIP)
    

Let's explore each one.

---

## 1\. Single Responsibility Principle (SRP)

> **"A class should have only one reason to change."**

### Before SRP (Bad)

```csharp
public class Invoice
{
    public decimal Total { get; set; }
    public List<InvoiceItem> Items { get; set; }
    
    public decimal CalculateTotal()
    {
        return Items.Sum(x => x.Price * x.Quantity);
    }
    
    public void SaveToDatabase()
    {
        // Database logic
    }
    
    public void GeneratePDF()
    {
        // PDF generation logic
    }
    
    public void SendEmail()
    {
        // Email logic
    }
}
```

**Problem:** `Invoice` has 4 responsibilities! Changes to PDF format, email templates, or database schema all affect this class.

### After SRP (Good)

```csharp
// Responsibility 1: Business logic
public class Invoice
{
    public decimal Total { get; set; }
    public List<InvoiceItem> Items { get; set; }
    
    public decimal CalculateTotal()
    {
        return Items.Sum(x => x.Price * x.Quantity);
    }
}

// Responsibility 2: Persistence
public class InvoiceRepository
{
    public void Save(Invoice invoice)
    {
        // Database logic
    }
}

// Responsibility 3: PDF generation
public class InvoicePdfGenerator
{
    public byte[] Generate(Invoice invoice)
    {
        // PDF generation logic
    }
}

// Responsibility 4: Notification
public class InvoiceEmailService
{
    public void Send(Invoice invoice, string email)
    {
        // Email logic
    }
}
```

**Benefits:**

* Each class has one reason to change
    
* Easy to test in isolation
    
* Easy to reuse (want to generate PDF for other entities? Reuse the pattern)
    

---

## 2\. Open/Closed Principle (OCP)

> **"Software entities should be open for extension but closed for modification."**

### Before OCP (Bad)

```csharp
public class DiscountCalculator
{
    public decimal CalculateDiscount(string customerType, decimal amount)
    {
        if (customerType == "Regular")
            return amount * 0.05m;
        else if (customerType == "Premium")
            return amount * 0.10m;
        else if (customerType == "VIP")
            return amount * 0.20m;
        
        return 0;
    }
}
```

**Problem:** Every new customer type requires modifying this class (violates "closed for modification").

### After OCP (Good)

```csharp
public interface IDiscountStrategy
{
    decimal CalculateDiscount(decimal amount);
}

public class RegularCustomerDiscount : IDiscountStrategy
{
    public decimal CalculateDiscount(decimal amount) => amount * 0.05m;
}

public class PremiumCustomerDiscount : IDiscountStrategy
{
    public decimal CalculateDiscount(decimal amount) => amount * 0.10m;
}

public class VIPCustomerDiscount : IDiscountStrategy
{
    public decimal CalculateDiscount(decimal amount) => amount * 0.20m;
}

public class DiscountCalculator
{
    private readonly IDiscountStrategy _strategy;
    
    public DiscountCalculator(IDiscountStrategy strategy)
    {
        _strategy = strategy;
    }
    
    public decimal Calculate(decimal amount)
    {
        return _strategy.CalculateDiscount(amount);
    }
}
```

**Benefits:**

* Add new discount types without modifying existing code
    
* Strategy pattern makes it extensible
    
* Easy to test each strategy independently
    

---

## 3\. Liskov Substitution Principle (LSP)

> **"Derived classes must be substitutable for their base classes."**

### Before LSP (Bad)

```csharp
public class Bird
{
    public virtual void Fly()
    {
        Console.WriteLine("Flying...");
    }
}

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

// Usage breaks LSP
Bird bird = new Penguin();
bird.Fly(); // 💥 Exception! Penguin violates LSP
```

### After LSP (Good)

```csharp
public abstract class Bird
{
    public abstract void Move();
}

public class FlyingBird : Bird
{
    public override void Move()
    {
        Console.WriteLine("Flying...");
    }
}

public class Penguin : Bird
{
    public override void Move()
    {
        Console.WriteLine("Swimming...");
    }
}

// Usage works correctly
Bird sparrow = new FlyingBird();
Bird penguin = new Penguin();
sparrow.Move(); // ✅ Flying...
penguin.Move(); // ✅ Swimming...
```

**Benefits:**

* Inheritance hierarchies make sense
    
* No unexpected exceptions
    
* Polymorphism works correctly
    

---

## 4\. Interface Segregation Principle (ISP)

> **"Clients should not be forced to depend on interfaces they don't use."**

### Before ISP (Bad)

```csharp
public interface IWorker
{
    void Work();
    void Eat();
    void Sleep();
}

public class Robot : IWorker
{
    public void Work() { /* ... */ }
    public void Eat() { throw new NotImplementedException(); } // ❌
    public void Sleep() { throw new NotImplementedException(); } // ❌
}
```

### After ISP (Good)

```csharp
public interface IWorkable
{
    void Work();
}

public interface IFeedable
{
    void Eat();
}

public interface ISleepable
{
    void Sleep();
}

public class Human : IWorkable, IFeedable, ISleepable
{
    public void Work() { /* ... */ }
    public void Eat() { /* ... */ }
    public void Sleep() { /* ... */ }
}

public class Robot : IWorkable
{
    public void Work() { /* ... */ }
}
```

**Benefits:**

* Smaller, focused interfaces
    
* Classes implement only what they need
    
* No "fat" interfaces
    

---

## 5\. Dependency Inversion Principle (DIP)

> **"Depend on abstractions, not concretions."**

### Before DIP (Bad)

```csharp
public class EmailService
{
    public void SendEmail(string to, string message)
    {
        // Send email
    }
}

public class NotificationService
{
    private EmailService _emailService = new EmailService(); // ❌ Tight coupling
    
    public void Notify(string message)
    {
        _emailService.SendEmail("user@example.com", message);
    }
}
```

### After DIP (Good)

```csharp
public interface INotificationSender
{
    void Send(string to, string message);
}

public class EmailService : INotificationSender
{
    public void Send(string to, string message)
    {
        // Send email
    }
}

public class SmsService : INotificationSender
{
    public void Send(string to, string message)
    {
        // Send SMS
    }
}

public class NotificationService
{
    private readonly INotificationSender _sender;
    
    public NotificationService(INotificationSender sender)
    {
        _sender = sender; // ✅ Depends on abstraction
    }
    
    public void Notify(string to, string message)
    {
        _sender.Send(to, message);
    }
}

// Usage with Dependency Injection
var emailNotifier = new NotificationService(new EmailService());
var smsNotifier = new NotificationService(new SmsService());
```

**Benefits:**

* Loose coupling
    
* Easy to swap implementations
    
* Testable (inject mocks)
    
* Foundation for Dependency Injection
    

---

## Real-World Example: Refactoring UserService

Let's refactor our initial `UserService` using SOLID:

```csharp
// SRP: Separate concerns
public interface IEmailValidator
{
    bool IsValid(string email);
}

public interface IPasswordHasher
{
    string Hash(string password);
}

public interface IUserRepository
{
    void Save(User user);
}

public interface INotificationService
{
    void SendWelcomeEmail(string email);
}

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

// OCP & DIP: Depend on abstractions
public class UserRegistrationService
{
    private readonly IEmailValidator _emailValidator;
    private readonly IPasswordHasher _passwordHasher;
    private readonly IUserRepository _userRepository;
    private readonly INotificationService _notificationService;
    private readonly ILogger _logger;
    
    public UserRegistrationService(
        IEmailValidator emailValidator,
        IPasswordHasher passwordHasher,
        IUserRepository userRepository,
        INotificationService notificationService,
        ILogger logger)
    {
        _emailValidator = emailValidator;
        _passwordHasher = passwordHasher;
        _userRepository = userRepository;
        _notificationService = notificationService;
        _logger = logger;
    }
    
    public void Register(string email, string password)
    {
        if (!_emailValidator.IsValid(email))
            throw new ArgumentException("Invalid email");
        
        var hashedPassword = _passwordHasher.Hash(password);
        var user = new User { Email = email, PasswordHash = hashedPassword };
        
        _userRepository.Save(user);
        _notificationService.SendWelcomeEmail(email);
        _logger.Log($"User {email} registered");
    }
}
```

**Now you can:**

* Unit test without database/email/file system
    
* Swap implementations (SQL → NoSQL, Email → SMS)
    
* Extend without modifying core logic
    

---

## Key Takeaways

1. **SRP**: One class, one responsibility
    
2. **OCP**: Extend behavior without modifying code
    
3. **LSP**: Subtypes must be substitutable for base types
    
4. **ISP**: Small, focused interfaces
    
5. **DIP**: Depend on abstractions, inject dependencies
    

**Pro Tip:** Apply SOLID when refactoring, not necessarily on first pass. Get it working, then make it clean.

---

## Practice Exercise

Refactor this code using SOLID principles:

```csharp
public class OrderProcessor
{
    public void ProcessOrder(Order order)
    {
        // Validate order
        if (order.Items.Count == 0) throw new Exception("Empty order");
        
        // Calculate total
        var total = order.Items.Sum(x => x.Price);
        
        // Charge credit card
        var cardProcessor = new CreditCardProcessor();
        cardProcessor.Charge(order.CardNumber, total);
        
        // Update inventory
        var db = new SqlConnection("...");
        // ...update database
        
        // Send confirmation email
        var smtp = new SmtpClient();
        smtp.Send(new MailMessage("..."));
    }
}
```

**Hint:** Identify responsibilities, create interfaces, inject dependencies.

---

## Resources

* **Book:** [Clean Code by Robert C. Martin](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)
    
* **Pluralsight:** [SOLID Principles of Object-Oriented Design](https://www.pluralsight.com/courses/principles-oo-design)
    
* **Microsoft Docs:** [Design Principles](https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/architectural-principles)
    
* **GitHub:** [SOLID Examples in C#](https://github.com/ardalis/SolidSample)
    

---

## What's Next?

In the next article, we'll dive deeper into **Dependency Injection in** [**ASP.NET**](http://ASP.NET) **Core** and see how the framework applies SOLID principles automatically.

**Question:** Which SOLID principle do you find hardest to apply? Let me know in the comments!

---

*Follow me for more clean code tips and .NET best practices. Happy coding! 🚀*
