SOLID

Good object-oriented design in C# follows the SOLID principles — a set of five guidelines that help keep classes small, focused, testable, and reusable.

S - Single Responsibility Principle (SRP)

O - Open/Closed Principle (OCP)

L - Liskov Substitution Principle (LSP)

I - Interface Segregation Principle (ISP)

D - Dependency Inversion Principle (DIP)

S — Single Responsibility Principle

A class should have one, and only one, reason to change.

Wrong — Multiple Responsibilities in One Class

public class Report
{
    public void Generate() { /* Generate report */ }
    public void SaveToFile() { /* Save to file */ }
    public void EmailReport() { /* Send via email */ }
}

Better — Following the Single Responsibility Principle (SRP)

public class ReportGenerator
{
    public string Generate() => "Report content";
}

public class FileSaver
{
    public void Save(string content) { /* Save to file */ }
}

public class EmailSender
{
    public void Send(string content) { /* Send via email */ }
}

O — Open/Closed Principle

A class should be open for extension, but closed for modification.

Wrong — Violating the Open/Closed Principle (OCP)

public class DiscountCalculator
{
    public double GetDiscount(string customerType)
    {
        if (customerType == "Regular") return 0.1;
        else if (customerType == "VIP") return 0.2;
        return 0;
    }
}

Problem:
If you ever want to add a new customer type, you’ll have to modify this class — breaking the Open/Closed Principle. Classes should be open for extension, but closed for modification.

Better — Following the Open/Closed Principle

public interface IDiscount
{
    double GetDiscount();
}

public class RegularCustomer : IDiscount
{
    public double GetDiscount() => 0.1;
}

public class VIPCustomer : IDiscount
{
    public double GetDiscount() => 0.2;
}

public class DiscountCalculator
{
    public double Calculate(IDiscount customer) => customer.GetDiscount();
}

L — Liskov Substitution Principle

Subtypes must be substitutable for their base types without altering the correctness of the program.

Wrong — Violating the Liskov Substitution Principle (LSP)

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

public class Sparrow : Bird { }

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

Better Design

public abstract class Bird { }

public interface IFlyingBird
{
    void Fly();
}

public class Sparrow : Bird, IFlyingBird
{
    public void Fly() => Console.WriteLine("Flying");
}

public class Penguin : Bird { }

I — Interface Segregation Principle

Clients should not be forced to depend on interfaces they do not use.

Wrong — Violating the Interface Segregation Principle (ISP)

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

Better Design

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() { }
}

D — Dependency Inversion Principle

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

Wrong — Direct Dependency

public class FileLogger
{
    public void Log(string msg) => File.WriteAllText("log.txt", msg);
}

public class OrderService
{
    private FileLogger _logger = new FileLogger(); // tightly coupled ❌
}

Better — Following the Dependency Inversion Principle (DIP)

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

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

public class OrderService
{
    private readonly ILogger _logger;

    public OrderService(ILogger logger)
    {
        _logger = logger;
    }
}