SOLID Principles in C#

The SOLID principles are five design principles introduced by Robert C. Martin (Uncle Bob) to make software more maintainable, scalable, and testable.

They are:

  1. Single Responsibility Principle (SRP)
  2. Open/Closed Principle (OCP)
  3. Liskov Substitution Principle (LSP)
  4. Interface Segregation Principle (ISP)
  5. Dependency Inversion Principle (DIP)

Let’s break each one down with bad (violating) and good (fixed) C# examples.


1. Single Responsibility Principle (SRP)

A class should have only one reason to change.

❌ Violation

public class Invoice
{
    public void CalculateTotal() { /* business logic */ }

    public void PrintInvoice() { /* printing logic */ }

    public void SaveToFile(string path) { /* persistence logic */ }
}

Here Invoice is doing three jobs:

  • business calculation
  • printing
  • persistence

If any of these responsibilities changes, Invoice must change → violation.

✅ Fix

public class Invoice
{
    public void CalculateTotal() { /* business logic */ }
}

public class InvoicePrinter
{
    public void Print(Invoice invoice) { /* printing logic */ }
}

public class InvoicePersistence
{
    public void SaveToFile(Invoice invoice, string path) { /* persistence logic */ }
}

Now each class has one reason to change.


2. Open/Closed Principle (OCP)

Classes should be open for extension but closed for modification.

❌ Violation

public class DiscountCalculator
{
    public decimal CalculateDiscount(string customerType, decimal amount)
    {
        if (customerType == "Regular")
            return amount * 0.1m;
        else if (customerType == "Premium")
            return amount * 0.2m;
        else
            return 0;
    }
}

If you add a new customer type, you must modify this method → breaks OCP.

✅ Fix (use polymorphism/specification)

public interface IDiscountRule
{
    decimal CalculateDiscount(decimal amount);
}

public class RegularDiscount : IDiscountRule
{
    public decimal CalculateDiscount(decimal amount) => amount * 0.1m;
}

public class PremiumDiscount : IDiscountRule
{
    public decimal CalculateDiscount(decimal amount) => amount * 0.2m;
}

public class DiscountCalculator
{
    private readonly Dictionary<string, IDiscountRule> rules;

    public DiscountCalculator()
    {
        rules = new Dictionary<string, IDiscountRule>
        {
            { "Regular", new RegularDiscount() },
            { "Premium", new PremiumDiscount() }
        };
    }

    public decimal CalculateDiscount(string customerType, decimal amount)
    {
        return rules.ContainsKey(customerType) ? rules[customerType].CalculateDiscount(amount) : 0;
    }
}

Now if a new type (e.g., VIP) is added, you add a new class, not modify existing code.


3. Liskov Substitution Principle (LSP)

Subtypes must be replaceable for their base type without breaking behavior.

❌ Violation

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

public class Ostrich : Bird
{
    public override void Fly()
    {
        throw new NotSupportedException("Ostriches can’t fly!");
    }
}

Now, code expecting any Bird to Fly() breaks with Ostrich.

✅ Fix

public abstract class Bird { }

public class FlyingBird : Bird
{
    public virtual void Fly() => Console.WriteLine("Flying...");
}

public class Sparrow : FlyingBird { }

public class Ostrich : Bird { }

Now, you don’t have broken contracts — Ostrich is not forced into Fly().


4. Interface Segregation Principle (ISP)

No client should be forced to implement methods it does not need.

❌ Violation

public interface IMachine
{
    void Print();
    void Scan();
    void Fax();
}

public class OldPrinter : IMachine
{
    public void Print() { Console.WriteLine("Printing..."); }
    public void Scan() { throw new NotImplementedException(); }
    public void Fax() { throw new NotImplementedException(); }
}

Here OldPrinter is forced to implement Scan and Fax which it doesn’t support.

✅ Fix

public interface IPrinter { void Print(); }
public interface IScanner { void Scan(); }
public interface IFax { void Fax(); }

public class OldPrinter : IPrinter
{
    public void Print() => Console.WriteLine("Printing...");
}

public class MultiFunctionPrinter : IPrinter, IScanner, IFax
{
    public void Print() => Console.WriteLine("Printing...");
    public void Scan() => Console.WriteLine("Scanning...");
    public void Fax() => Console.WriteLine("Faxing...");
}

Now clients implement only what they need.


5. Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules. Both should depend on abstractions.

❌ Violation

public class FileLogger
{
    public void Log(string message)
    {
        File.AppendAllText("log.txt", message);
    }
}

public class UserService
{
    private readonly FileLogger logger = new FileLogger();

    public void RegisterUser(string username)
    {
        // business logic
        logger.Log("User registered: " + username);
    }
}

UserService depends on FileLogger (a concrete class). If tomorrow you need DB logging, you must modify UserService.

✅ Fix

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

public class FileLogger : ILogger
{
    public void Log(string message) =>
        File.AppendAllText("log.txt", message);
}

public class DatabaseLogger : ILogger
{
    public void Log(string message) =>
        Console.WriteLine($"DB log: {message}");
}

public class UserService
{
    private readonly ILogger logger;

    public UserService(ILogger logger)
    {
        this.logger = logger;
    }

    public void RegisterUser(string username)
    {
        // business logic
        logger.Log("User registered: " + username);
    }
}

Now, by injecting a different ILogger, you can switch logging without changing UserService.


🎯 Summary (for interviews)

  • SRP: One reason to change. (Separate responsibilities)
  • OCP: Extend without modifying existing code. (Polymorphism/specifications)
  • LSP: Subtypes should respect base contracts.
  • ISP: Favor many small, specific interfaces over one fat interface.
  • DIP: Depend on abstractions, not concretes.

Post a Comment

Contact Form