SOLID Principles 🧱
The Secret to Clean, Professional Code
Hey There! Ready to Learn SOLID Principles? 🏗️
SOLID is a set of 5 design principles that help you write clean, maintainable, and professional code. It's like the golden rules of building great software!
- 🏗️ Easy to maintain - Change code without breaking things
- 🧩 Easy to extend - Add new features without touching old code
- 👥 Team-friendly - Multiple developers can work together
- 🐛 Fewer bugs - Clean code = fewer problems
- ✅ Understand all 5 SOLID principles
- ✅ Spot bad code that violates SOLID
- ✅ Write clean, professional code
- ✅ Build applications that are easy to maintain
- ✅ Impress your team with your coding skills!
Part 1: What are SOLID Principles?
📝 The SOLID Acronym
| Letter | Principle | Simple Meaning |
|---|---|---|
| S | Single Responsibility | One job per class |
| O | Open/Closed | Add new stuff, don't change old stuff |
| L | Liskov Substitution | Children should act like their parents |
| I | Interface Segregation | Don't force things they don't need |
| D | Dependency Inversion | Depend on ideas, not specific things |
Part 2: S - Single Responsibility Principle
❌ Bad Example
One class doing EVERYTHING:
public class Employee
{
// 1. Stores data
public string Name { get; set; }
// 2. Calculates bonus
public decimal CalcBonus() => Salary * 0.1m;
// 3. Saves to database
public void Save() { }
// 4. Sends emails
public void SendEmail() { }
}
⚠️ Four responsibilities in one class!
✅ Good Example
Each class has ONE job:
// Job 1: Store data
public class Employee
{
public string Name { get; set; }
}
// Job 2: Calculate bonus
public class BonusCalculator
{
public decimal Calc(Employee e) => e.Salary * 0.1m;
}
// Job 3: Save data
public class EmployeeRepo
{
public void Save(Employee e) { }
}
✅ Each class has ONE responsibility!
Part 3: O - Open/Closed Principle
❌ Bad Example
Must change existing code to add new shapes:
public class AreaCalculator
{
public double Area(object shape)
{
if (shape is Circle c)
return Math.PI * c.Radius * c.Radius;
else if (shape is Rectangle r)
return r.Width * r.Height;
// Adding new shapes requires changing THIS method!
return 0;
}
}
✅ Good Example
Add new shapes without changing existing code:
// Base class - open for extension
public abstract class Shape
{
public abstract double Area();
}
// Each shape in its own class
public class Circle : Shape
{
public override double Area() => Math.PI * Radius * Radius;
}
✅ Add new shapes by creating new classes!
Part 4: L - Liskov Substitution Principle
❌ Bad Example
Square breaking Rectangle behavior:
public class Rectangle
{
public virtual int Width { get; set; }
public virtual int Height { get; set; }
}
public class Square : Rectangle
{
public override int Width
{
set { base.Width = value; base.Height = value; }
}
}
⚠️ Square changes both width AND height!
✅ Good Example
Separate classes for different shapes:
public abstract class Shape
{
public abstract int Area();
}
public class Rectangle : Shape
{
public int Width { get; set; }
public int Height { get; set; }
public override int Area() => Width * Height;
}
public class Square : Shape
{
public int Side { get; set; }
public override int Area() => Side * Side;
}
✅ Each shape is separate and works correctly!
Part 5: I - Interface Segregation Principle
❌ Bad Example
One big interface with everything:
public interface IWorker
{
void Work();
void Eat();
void Sleep();
}
// Robot forced to Eat and Sleep!
public class Robot : IWorker
{
public void Work() { }
public void Eat() { } // Robots don't eat!
public void Sleep() { } // Robots don't sleep!
}
✅ Good Example
Small, focused interfaces:
public interface IWorkable
{
void Work();
}
public interface IEatable
{
void Eat();
}
public interface ISleepable
{
void Sleep();
}
// Robot only implements what it needs
public class Robot : IWorkable
{
public void Work() { }
}
✅ Each class implements only what it needs!
Part 6: D - Dependency Inversion Principle
❌ Bad Example
High-level class depends on low-level class:
public class EmailSender
{
public void Send(string msg) { }
}
// ❌ Depends on specific EmailSender
public class Notification
{
private EmailSender sender = new EmailSender();
public void Notify() => sender.Send("Hi");
}
✅ Good Example
Depend on abstraction (interface):
public interface IMessageSender
{
void Send(string msg);
}
public class EmailSender : IMessageSender
{
public void Send(string msg) { }
}
// ✅ Depends on interface, not concrete class
public class Notification
{
private readonly IMessageSender sender;
public Notification(IMessageSender sender) =>
this.sender = sender;
public void Notify() => sender.Send("Hi");
}
✅ Can switch to SMS, Push, etc. without changing Notification!
Part 7: Complete Real-World Example - Library System 📚
Let's build a Library Management System using all SOLID principles!
// ===== SOLID Library System =====
// 1. SRP - Each class has one job
// 2. OCP - Add new book types easily
// 3. LSP - All books work the same way
// Interfaces (ISP - small, focused)
public interface IBook
{
string Title { get; }
string Author { get; }
}
public interface IBorrowable
{
void Borrow();
void Return();
bool IsAvailable { get; }
}
public interface IPrintable
{
void Print();
}
// Abstract base class (OCP - open for extension)
public abstract class Book : IBook, IBorrowable, IPrintable
{
public string Title { get; set; }
public string Author { get; set; }
public bool IsAvailable { get; private set; }
public Book(string title, string author)
{
Title = title;
Author = author;
IsAvailable = true;
}
public void Borrow()
{
if (!IsAvailable)
throw new Exception("Book already borrowed");
IsAvailable = false;
}
public void Return() => IsAvailable = true;
public abstract void Print();
}
// Different book types (LSP - substitutable)
public class PhysicalBook : Book
{
public int Pages { get; set; }
public PhysicalBook(string title, string author, int pages)
: base(title, author) => Pages = pages;
public override void Print() =>
Console.WriteLine($"📖 Physical: {Title} by {Author} ({Pages} pages)");
}
public class EBook : Book
{
public string FileSize { get; set; }
public EBook(string title, string author, string size)
: base(title, author) => FileSize = size;
public override void Print() =>
Console.WriteLine($"📱 E-Book: {Title} by {Author} ({FileSize} MB)");
}
// Services (DIP - depend on abstractions)
public interface ILibrary
{
void AddBook(Book book);
void DisplayAll();
}
public class Library : ILibrary
{
private List<Book> books = new List<Book>();
public void AddBook(Book book) => books.Add(book);
public void DisplayAll()
{
foreach (var book in books)
book.Print();
}
}
// Program (DIP - uses abstractions)
class Program
{
static void Main()
{
ILibrary library = new Library();
library.AddBook(new PhysicalBook("C# Guide", "John Doe", 500));
library.AddBook(new EBook("Clean Code", "Robert Martin", "2.5"));
library.DisplayAll();
}
}
- ✅ SRP - Each class has one job
- ✅ OCP - Add new book types easily
- ✅ LSP - All books work the same way
- ✅ ISP - Small, focused interfaces
- ✅ DIP - Depend on abstractions
Part 8: Let's Practice! 🎮
Support multiple payment methods (Credit Card, PayPal, Crypto) with clean design.
💳 About This Application
This is a Payment System like:
- 💳 Stripe - Credit card payments
- 💸 PayPal - Digital wallets
- 🪙 Coinbase - Crypto payments
You'll build it using SOLID principles:
- ✅ Each payment method is its own class
- ✅ Easy to add new payment methods
- ✅ Clean, testable code
Your Mission:
-
Interfaces:
- IPayment - Process()
- IRefundable - Refund()
- IReceiptable - GetReceipt()
-
Abstract Class: Payment
- Amount property
- Implement IPayment
-
Derived Classes:
- CreditCardPayment
- PayPalPayment
- CryptoPayment
-
Service: PaymentProcessor
- Accepts any IPayment
- Processes and generates receipt
💡 SOLID Applied:
- ✅ SRP - Each class has one job
- ✅ OCP - Add new payment types easily
- ✅ LSP - All payments work the same
- ✅ ISP - Small, focused interfaces
- ✅ DIP - Depend on IPayment interface
// ===== SOLID PAYMENT SYSTEM =====
// 1. ISP - Small, focused interfaces
public interface IPayment
{
decimal Amount { get; }
void Process();
}
public interface IRefundable
{
void Refund();
}
public interface IReceiptable
{
string GetReceipt();
}
// 2. Abstract base class (OCP - open for extension)
public abstract class Payment : IPayment, IReceiptable
{
public decimal Amount { get; set; }
protected string TransactionId { get; set; }
public Payment(decimal amount) => Amount = amount;
public abstract void Process();
public virtual string GetReceipt() =>
$"Transaction: {TransactionId}\nAmount: ${Amount:F2}";
}
// 3. Payment implementations (LSP - substitutable)
public class CreditCardPayment : Payment, IRefundable
{
public string CardNumber { get; set; }
public CreditCardPayment(decimal amount, string card)
: base(amount) => CardNumber = card;
public override void Process()
{
TransactionId = $"CC-{DateTime.Now.Ticks}";
Console.WriteLine($"💳 Charging ${Amount:F2} to card ending in {CardNumber[^4..]}");
}
public void Refund() =>
Console.WriteLine($"🔄 Refunding ${Amount:F2} to card");
}
public class PayPalPayment : Payment, IRefundable
{
public string Email { get; set; }
public PayPalPayment(decimal amount, string email)
: base(amount) => Email = email;
public override void Process()
{
TransactionId = $"PP-{DateTime.Now.Ticks}";
Console.WriteLine($"💸 Sending ${Amount:F2} to {Email}");
}
public void Refund() =>
Console.WriteLine($"🔄 Refunding ${Amount:F2} to {Email}");
}
public class CryptoPayment : Payment
{
public string WalletAddress { get; set; }
public CryptoPayment(decimal amount, string wallet)
: base(amount) => WalletAddress = wallet;
public override void Process()
{
TransactionId = $"CR-{DateTime.Now.Ticks}";
Console.WriteLine($"🪙 Sending ${Amount:F2} worth of crypto to {WalletAddress[^8..]}");
}
}
// 4. Service (SRP - single responsibility)
public class PaymentProcessor
{
private List<IPayment> payments = new List<IPayment>();
public void AddPayment(IPayment payment) => payments.Add(payment);
public void ProcessAll()
{
foreach (var payment in payments)
{
payment.Process();
if (payment is IReceiptable receipt)
Console.WriteLine(receipt.GetReceipt());
Console.WriteLine();
}
}
public decimal GetTotal() =>
payments.Sum(p => p.Amount);
}
// 5. Program (DIP - depends on abstractions)
class Program
{
static void Main()
{
Console.WriteLine("💳 PAYMENT SYSTEM\n");
PaymentProcessor processor = new PaymentProcessor();
// Add different payments
processor.AddPayment(new CreditCardPayment(100.00m, "1234-5678-9012-3456"));
processor.AddPayment(new PayPalPayment(75.00m, "user@email.com"));
processor.AddPayment(new CryptoPayment(50.00m, "0x1234...5678"));
// Process all
processor.ProcessAll();
Console.WriteLine($"💰 Total: ${processor.GetTotal():F2}");
}
}