Software development is not just about writing code; it’s about writing clean, maintainable, scalable, and testable code. That’s exactly what the SOLID principles help us achieve.
Whether you’re building a small API or an enterprise-level microservice, understanding SOLID principles makes your code more flexible, easier to extend, and less prone to bugs.
In this article, we’ll break down SOLID principles with simple explanations and practical C# implementations.
What is the SOLID Principle?
The SOLID Principles are a set of five core object-oriented design (OOD) guidelines used in software development to create systems that are more understandable, flexible, maintainable, and scalable. Coined by Michael Feathers and based on principles from Robert C. Martin ("Uncle Bob").
SOLID is an acronym for five object-oriented design principles:
- S: Single Responsibility Principle
- O: Open/Closed Principle
- L: Liskov Substitution Principle
- I: Interface Segregation Principle
- D: Dependency Inversion Principle
Let’s go through each principle one by one and look at the problems that arise when we don’t follow the SOLID Principle. After that, we’ll explore how applying these principles can help us write cleaner, more maintainable code.
1. S - Single Responsibility Principle.
The 'S' in the SOLID principles stands for the Single Responsibility Principle (SRP). The Single Responsibility Principle states that a class, module, or function should have only one reason to change, meaning it should have a single, clearly defined purpose or job within the software system.
Bad Example: Imagine an online shopping system where:
- An order is created
- Order is saved to the database
- Customer is emailed
If all of this is in ONE class, it violates SRP.
Example Code:
public class OrderService { public void CreateOrder() { Console.WriteLine("Order created."); } public void SaveToDatabase() { Console.WriteLine("Order saved to DB."); } public void SendEmail() { Console.WriteLine("Email sent to customer."); } }
namespace PracticeCode.SOLID { public class OrderCreator { public void CreateOrder() { Console.WriteLine("Order created."); } } public class OrderRepository { public void Save() { Console.WriteLine("Order saved to DB."); } } public class EmailService { public void Send() { Console.WriteLine("Email sent to customer."); } } public class OrderService { private readonly OrderCreator _orderCreator; private readonly OrderRepository _orderRepository; private readonly EmailService _emailService; public OrderService() { _orderCreator = new OrderCreator(); _orderRepository = new OrderRepository(); _emailService = new EmailService(); } public void ProcessOrder() { _orderCreator.CreateOrder(); _orderRepository.Save(); _emailService.Send(); } } }
OrderService processor = new OrderService(); processor.ProcessOrder(); Console.ReadLine();
Order created.
Order saved to DB.
Email sent to customer.
2. O - Open/Closed Principle (OCP)
public class DiscountService { public decimal GetDiscount(string customerType, decimal amount) { if (customerType == "Regular") return amount * 0.05m; if (customerType == "Premium") return amount * 0.10m; return 0; } }
public abstract class Discount { public abstract decimal Calculate(decimal amount); }
public class RegularDiscount : Discount { public override decimal Calculate(decimal amount) => amount * 0.05m; } public class PremiumDiscount : Discount { public override decimal Calculate(decimal amount) => amount * 0.10m; }
public class GoldDiscount : Discount { public override decimal Calculate(decimal amount) => amount * 0.15m; }
//----------------Discount Factory----------------// public static class DiscountFactory { public static Discount GetDiscount(string discountType) { return discountType.ToLower() switch { "regular" => new RegularDiscount(), "seasonal" => new SeasonalDiscount(), _ => throw new ArgumentException("Invalid discount type"), }; } } public class Customer { private readonly Discount _discount; public Customer(Discount discount) { _discount = discount; } public decimal GetFinalAmount(decimal amount) { return _discount.ApplyDiscount(amount); } }
class Program { static void Main(string[] args) { Console.Write("Enter discount type (regular / seasonal): "); string type = Console.ReadLine(); Discount discount = DiscountFactory.CreateDiscount(type); Customer customer = new Customer(discount); decimal amount = 1000; decimal finalAmount = customer.GetFinalAmount(amount); Console.WriteLine($"Final amount for {type} discount: {finalAmount}"); } }
Enter discount type (regular / seasonal): seasonal
Final amount for seasonal discount: 800
3. L - Liskov Substitution Principle (LSP)
public class Payment { public virtual void Pay(decimal amount) { Console.WriteLine($"Payment of {amount} done."); } public virtual void Refund(decimal amount) { Console.WriteLine($"Refund of {amount} processed."); } }
public class CashPayment : Payment { public override void Refund(decimal amount) { throw new NotSupportedException("Cash cannot be refunded!"); } }
Payment payment = new CashPayment(); payment.Refund(100); // 💥 Crash at runtime!
namespace PracticeCode.SOLID { public interface IPayment { void Pay(decimal amount); } public interface IRfundable { void Refund(decimal amount); } public class CardPayment: IPayment, IRfundable { public void Pay(decimal amount) => Console.WriteLine($"Card Amount Paid: {amount}"); public void Refund(decimal amount) => Console.WriteLine($"Card Amount Refund: {amount}"); } public class CashPayment: IPayment { public void Pay(decimal amount) => Console.WriteLine($"Cash Amount Paid: {amount}"); } public class PaymentProcess { public void ProcessPayment(IPayment payment, decimal amount) { payment.Pay(amount); } public void RefundPayment(IRfundable refund, decimal amount) { refund.Refund(amount); } } }
//LSP var processPayment = new PaymentProcess(); processPayment.ProcessPayment(new CardPayment(), 100); processPayment.ProcessPayment(new CashPayment(), 100); processPayment.RefundPayment(new CardPayment(), 100); //processPayment.RefundPayment(new CashPayment(), 100); //Compile time error
4. I - Interface Segregation Principle (ISP)
public interface IWorker { void Work(); void Eat(); } public class Robot : IWorker { public void Work() { Console.WriteLine("Robot working."); } public void Eat() { throw new NotImplementedException("Robots don't eat!"); } }
public interface IWorkable { void Work(); } public interface IFeedable { void Eat(); } // Now classes implement only what they need public class Human : IWorkable, IFeedable { public void Work() => Console.WriteLine("Human working."); public void Eat() => Console.WriteLine("Human eating."); } public class Robot : IWorkable { public void Work() => Console.WriteLine("Robot working."); }
namespace PracticeCode.SOLID { class Program { static void Main(string[] args) { // Using IWorkable interface IWorkable humanWorker = new Human(); IWorkable robotWorker = new Robot(); humanWorker.Work(); // Output: Human working. robotWorker.Work(); // Output: Robot working. Console.WriteLine(); // Using IFeedable interface IFeedable humanEater = new Human(); humanEater.Eat(); // Output: Human eating. // Robot does NOT implement IFeedable // So you cannot do this: // IFeedable robotEater = new Robot(); // ❌ Compile-time error (Good!) Console.ReadLine(); } } }
5. D - Dependency Inversion Principle (DIP)
public class EmailService { public void Send(string message) { Console.WriteLine("EMAIL: " + message); } } public class NotificationService { private readonly EmailService _emailService = new EmailService(); // ❌ Hard-coded dependency public void Notify(string message) { _emailService.Send(message); } }
public interface IMessageService { void Send(string message); } public class EmailService : IMessageService { public void Send(string message) { Console.WriteLine("EMAIL: " + message); } } public class SmsService : IMessageService { public void Send(string message) { Console.WriteLine("SMS: " + message); } }
public class NotificationService { private readonly IMessageService _messageService; public NotificationService(IMessageService messageService) { _messageService = messageService; } public void Notify(string message) { _messageService.Send(message); } }
class Program { static void Main(string[] args) { IMessageService service = new SmsService(); // choose service NotificationService notifier = new NotificationService(service); notifier.Notify("Order shipped!"); // SMS: Order shipped! Console.ReadLine(); } }
- Scalable
- Maintainable
- Testable
- Professional
If you're preparing for interviews, mastering SOLID is one of the biggest advantages you can have.

