S: Single Responsibility Principle
Definition: A class should have only one reason to change.
Example: Separating user authentication and email notification.
public class UserAuth
public string Login(string username, string password)
// Authentication logic
return "User logged in";
public class EmailNotification
public string SendEmail(string email, string message)
// Email sending logic
return $"Email sent to {email}";
Each class has a single responsibility.
O: Open/Closed Principle
Definition: Classes should be open for extension but closed for modification.
Example: Adding new shapes without changing existing code.
public abstract class Shape
public abstract double Area();
public class Rectangle : Shape
public double Width { get; set; }
public double Height { get; set; }
public override double Area()
return Width * Height;
public class Circle : Shape
public double Radius { get; set; }
public override double Area()
return Math.PI * Radius * Radius;
}
// Usage
var shapes = new List<Shape>
new Rectangle { Width = 5, Height = 10 },
new Circle { Radius = 7 }
};
double totalArea = shapes.Sum(shape => shape.Area());
Console.WriteLine($"Total Area: {totalArea}");
Adding new shapes like Triangle doesn’t require modifying existing code.
L: Liskov Substitution Principle
Definition: Subtypes should be substitutable for their base types.
Example: A subclass should not break the behavior of its parent class.
public abstract class Bird
public abstract string Fly();
public class Sparrow : Bird
public override string Fly()
return "I am flying!";
public class Penguin : Bird // Violates Liskov because Penguins can't fly
public override string Fly()
throw new NotImplementedException("Penguins can't fly");
// Correct approach
public class NonFlyingBird : Bird
public override string Fly()
{
return "I can't fly";
// Usage
Bird penguin = new NonFlyingBird();
Console.WriteLine(penguin.Fly());
I: Interface Segregation Principle
Definition: A class should not be forced to implement methods it doesn’t use.
Example: Splitting a large interface into smaller ones.
public interface IPrinter
void Print();
public interface IScanner
void Scan();
public class AllInOnePrinter : IPrinter, IScanner
public void Print()
Console.WriteLine("Printing...");
public void Scan()
Console.WriteLine("Scanning...");
public class BasicPrinter : IPrinter
{
public void Print()
Console.WriteLine("Printing...");
A BasicPrinter only implements printing, not scanning.
D: Dependency Inversion Principle
Definition: High-level modules should not depend on low-level modules. Both should depend on
abstractions.
Example: Using abstraction for message delivery.
// The interface represents an abstraction for sending messages
public interface IMessageService
void SendMessage(string user, string message);
// Simple EmailService implementation
public class EmailService : IMessageService
public void SendMessage(string user, string message)
Console.WriteLine($"Email sent to {user}: {message}");
// Simple SMSService implementation
public class SMSService : IMessageService
public void SendMessage(string user, string message)
Console.WriteLine($"SMS sent to {user}: {message}");
}
// Notification class depends on the abstraction IMessageService
public class Notification
private readonly IMessageService _messageService;
// Constructor injection to provide the dependency
public Notification(IMessageService messageService)
_messageService = messageService;
// Notify method delegates message sending to the injected service
public void Notify(string user, string message)
_messageService.SendMessage(user, message);
// Usage Example
class Program
static void Main()
// Create EmailService and SMSService separately, and pass them to Notification
var emailNotification = new Notification(new EmailService());
emailNotification.Notify("user@example.com", "Hello via Email!");
var smsNotification = new Notification(new SMSService());
smsNotification.Notify("1234567890", "Hello via SMS!");
}
Key Simplifications:
Removed string returns: Instead of returning strings from SendMessage, I simplified it to just print
directly, making it easier to follow.
Constructor Injection: The Notification class still receives the IMessageService via constructor injection.
This keeps the code simple but still demonstrates the principle.
How Dependency Inversion Works Here:
Notification (high-level module) does not directly depend on EmailService or SMSService (low-level
modules).
Instead, it depends on the IMessageService interface, which is an abstraction.
We can easily switch out EmailService and SMSService without modifying Notification.