IConfiguration vs IOptions NET
Synchronous and Asynchronous in .NET Core
Model Binding and Validation in ASP.NET Core
ControllerBase vs Controller in ASP.NET Core
ConfigureServices and Configure methods
IHostedService interface in .NET Core
ASP.NET Core request processing
| Factory-vs-Abstract-Factory-Pattern | Question Answer Home Page | |
SOLID Principles in C# |
Your guide to building maintainable, scalable, and testable .NET applications.
SOLID is an acronym for five design principles that help you write cleaner, more robust object-oriented code. Coined by Robert C. Martin, these guidelines guard against rigid, fragile, and tightly coupled designs.
Mnemonic for SOLID:
A class should have one, and only one, reason to change. Grouping unrelated responsibilities leads to complex classes that break whenever any feature changes.
// Bad: One class does everything
public class InvoiceManager
{
public void Print(Invoice inv) { /*...*/ }
public void Save(Invoice inv) { /*...*/ }
}
// Good: Separate responsibilities
public class InvoicePrinter
{
public void Print(Invoice inv) { /* print logic */ }
}
public class InvoiceRepository
{
public void Save(Invoice inv) { /* db logic */ }
}
SRP often leads to more classes but each with a laser-focused role. This improves testability and reduces merge conflicts.
Entities should be open for extension, but closed for modification. You should be able to add new behavior without touching existing code.
// Abstract base
public abstract class DiscountStrategy
{
public abstract decimal Apply(decimal total);
}
// No discount
public class NoDiscount : DiscountStrategy
{
public override decimal Apply(decimal total) => total;
}
// 10% off
public class TenPercentDiscount : DiscountStrategy
{
public override decimal Apply(decimal total) => total * 0.9m;
}
// New strategies? Just extend DiscountStrategy.
Derived types must be substitutable for their base types. If a subclass breaks behavior, clients expecting the base type will fail.
// Violates LSP
public class Bird
{
public virtual void Fly() { /*...*/ }
}
public class Ostrich : Bird
{
public override void Fly() =>
throw new NotSupportedException();
}
public abstract class Bird { }
public class FlyingBird : Bird
{
public void Fly() { /*...*/ }
}
public class Ostrich : Bird
{
// No Fly method needed
}
No client should depend on methods it does not use. Large interfaces force classes to implement irrelevant members.
// Segregated interfaces
public interface IPrinter
{
void Print(Document doc);
}
public interface IScanner
{
void Scan(Document doc);
}
// Implement only what you need
public class SimplePrinter : IPrinter
{
public void Print(Document doc) { /*...*/ }
}
public class AllInOne : IPrinter, IScanner
{
public void Print(Document doc) { /*...*/ }
public void Scan(Document doc) { /*...*/ }
}
High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions.
// Abstraction
public interface IMessageSender
{
void Send(string message);
}
// Low-level
public class EmailSender : IMessageSender
{
public void Send(string message) =>
Console.WriteLine("Email: " + message);
}
// High-level
public class NotificationService
{
private readonly IMessageSender _sender;
public NotificationService(IMessageSender sender) =>
_sender = sender;
public void Notify(string msg) =>
_sender.Send(msg);
}
| Principle | Key Idea | C# Example |
|---|---|---|
| SRP | One class, one responsibility | InvoicePrinter, InvoiceRepository |
| OCP | Extend, don’t modify | DiscountStrategy subtypes |
| LSP | Subtypes must behave like their base | Separate FlyingBird vs Ostrich |
| ISP | Many small interfaces | IPrinter, IScanner |
| DIP | Depend on abstractions | IMessageSender + DI |
Imagine a retail application where pricing, tax calculations, and shipping vary by region. Applying SOLID lets you isolate each concern:
Many GoF patterns complement SOLID:
SOLID code is inherently testable:
// Example: mocking IMessageSender var mockSender = new Mock(); var service = new NotificationService(mockSender.Object); service.Notify("Hello"); mockSender.Verify(m => m.Send("Hello"), Times.Once);
| Factory-vs-Abstract-Factory-Pattern | Question Answer Home Page | |