The four pillars of Object-Oriented Programming are: Encapsulation, Abstraction, Inheritance, and Polymorphism.
1. Encapsulation:
Bundling data (fields) and methods that operate on that data within a single unit (class), and restricting direct access to some components.
Real-world analogy: A car's engine - you don't need to know how the engine works internally, you just use the gas pedal and steering wheel (public interface).
public class BankAccount
{
// Private fields - hidden from outside
private decimal balance;
private string accountNumber;
private List<Transaction> transactions;
// Public constructor
public BankAccount(string accountNumber, decimal initialBalance)
{
this.accountNumber = accountNumber;
this.balance = initialBalance;
transactions = new List<Transaction>();
}
// Public property with validation
public decimal Balance
{
get { return balance; }
private set // Can only be set internally
{
if (value < 0)
throw new InvalidOperationException("Balance cannot be negative");
balance = value;
}
}
// Public methods - controlled interface
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Amount must be positive");
Balance += amount;
AddTransaction("Deposit", amount);
}
public bool Withdraw(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Amount must be positive");
if (balance >= amount)
{
Balance -= amount;
AddTransaction("Withdrawal", -amount);
return true;
}
return false;
}
// Private helper method
private void AddTransaction(string type, decimal amount)
{
transactions.Add(new Transaction
{
Date = DateTime.Now,
Type = type,
Amount = amount
});
}
}
// Usage - clean public interface, implementation hidden
BankAccount account = new BankAccount("123456", 1000);
account.Deposit(500); // OK
account.Withdraw(200); // OK
// account.balance = -1000; // ERROR - can't access private field
// account.Balance = -1000; // ERROR - setter is private
Benefits:
- Data protection and validation
- Flexibility to change implementation
- Controlled access
- Maintainability
2. Abstraction:
Hiding complex implementation details and showing only essential features. Focusing on "what" rather than "how".
Real-world analogy: Using a smartphone - you tap icons to make calls, send messages, but don't need to understand the underlying cellular technology.
// Abstract class - defines contract
public abstract class PaymentProcessor
{
// Abstract methods - must be implemented
public abstract bool ProcessPayment(decimal amount);
public abstract string GetPaymentMethod();
// Concrete method - shared implementation
public void LogTransaction(decimal amount)
{
Console.WriteLine($"{GetPaymentMethod()}: ${amount} at {DateTime.Now}");
}
// Template method pattern
public bool MakePayment(decimal amount)
{
if (ValidateAmount(amount))
{
bool success = ProcessPayment(amount);
if (success)
{
LogTransaction(amount);
}
return success;
}
return false;
}
private bool ValidateAmount(decimal amount)
{
return amount > 0;
}
}
// Concrete implementations
public class CreditCardProcessor : PaymentProcessor
{
private string cardNumber;
public CreditCardProcessor(string cardNumber)
{
this.cardNumber = cardNumber;
}
public override bool ProcessPayment(decimal amount)
{
// Credit card specific logic
Console.WriteLine($"Processing credit card payment: {cardNumber}");
return true; // Simplified
}
public override string GetPaymentMethod()
{
return "Credit Card";
}
}
public class PayPalProcessor : PaymentProcessor
{
private string email;
public PayPalProcessor(string email)
{
this.email = email;
}
public override bool ProcessPayment(decimal amount)
{
// PayPal specific logic
Console.WriteLine($"Processing PayPal payment: {email}");
return true; // Simplified
}
public override string GetPaymentMethod()
{
return "PayPal";
}
}
// Usage - work with abstraction, not concrete types
public class CheckoutService
{
public void Checkout(PaymentProcessor processor, decimal amount)
{
// Don't care about implementation details
if (processor.MakePayment(amount))
{
Console.WriteLine("Payment successful!");
}
}
}
// Client code
CheckoutService checkout = new CheckoutService();
checkout.Checkout(new CreditCardProcessor("1234-5678"), 99.99m);
checkout.Checkout(new PayPalProcessor("user@email.com"), 49.99m);
Benefits:
- Reduces complexity
- Easier to understand and use
- Changes in implementation don't affect users
- Promotes code reusability
3. Inheritance:
Mechanism where a new class derives properties and behaviors from an existing class, establishing a parent-child relationship.
Real-world analogy: Biological inheritance - children inherit characteristics from parents (eye color, height), but can have their own unique traits.
// Base class
public class Vehicle
{
public string Make { get; set; }
public string Model { get; set; }
public int Year { get; set; }
protected bool isEngineRunning;
public Vehicle(string make, string model, int year)
{
Make = make;
Model = model;
Year = year;
}
public virtual void Start()
{
isEngineRunning = true;
Console.WriteLine($"{Make} {Model} engine started");
}
public virtual void Stop()
{
isEngineRunning = false;
Console.WriteLine($"{Make} {Model} engine stopped");
}
public void DisplayInfo()
{
Console.WriteLine($"{Year} {Make} {Model}");
}
}
// Derived class - inherits from Vehicle
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public string TransmissionType { get; set; }
public Car(string make, string model, int year, int doors, string transmission)
: base(make, model, year) // Call parent constructor
{
NumberOfDoors = doors;
TransmissionType = transmission;
}
// Override parent method
public override void Start()
{
Console.WriteLine("Checking car systems...");
base.Start(); // Call parent implementation
Console.WriteLine("Car is ready to drive");
}
// New method specific to Car
public void OpenTrunk()
{
Console.WriteLine("Trunk opened");
}
}
// Another derived class
public class Motorcycle : Vehicle
{
public bool HasSidecar { get; set; }
public Motorcycle(string make, string model, int year, bool hasSidecar)
: base(make, model, year)
{
HasSidecar = hasSidecar;
}
public override void Start()
{
Console.WriteLine("Kick-starting motorcycle...");
base.Start();
}
// New method specific to Motorcycle
public void Wheelie()
{
Console.WriteLine("Performing wheelie!");
}
}
// Derived class from Car
public class ElectricCar : Car
{
public int BatteryCapacity { get; set; }
public ElectricCar(string make, string model, int year, int doors, int batteryCapacity)
: base(make, model, year, doors, "Automatic")
{
BatteryCapacity = batteryCapacity;
}
public override void Start()
{
Console.WriteLine("Electric car booting up...");
isEngineRunning = true; // Can access protected member
Console.WriteLine("Ready to drive - silent mode");
}
public void Charge()
{
Console.WriteLine("Charging battery...");
}
}
// Usage
Vehicle vehicle1 = new Car("Toyota", "Camry", 2023, 4, "Automatic");
Vehicle vehicle2 = new Motorcycle("Harley", "Street 750", 2023, false);
Vehicle vehicle3 = new ElectricCar("Tesla", "Model 3", 2023, 4, 75);
vehicle1.Start(); // Calls Car.Start()
vehicle2.Start(); // Calls Motorcycle.Start()
vehicle3.Start(); // Calls ElectricCar.Start()
Benefits:
- Code reusability (DRY principle)
- Hierarchical classification
- Extensibility
- Polymorphic behavior
4. Polymorphism:
Ability of objects to take multiple forms. Same interface, different implementations.
Real-world analogy: A person can be a student, employee, athlete simultaneously - same person, different roles/behaviors in different contexts.
Types of Polymorphism:
a) Compile-time Polymorphism (Method Overloading):
public class Calculator
{
// Same method name, different parameters
public int Add(int a, int b)
{
return a + b;
}
public double Add(double a, double b)
{
return a + b;
}
public int Add(int a, int b, int c)
{
return a + b + c;
}
public string Add(string a, string b)
{
return a + b;
}
}
// Usage - compiler decides which method to call
Calculator calc = new Calculator();
int result1 = calc.Add(5, 3); // Calls Add(int, int)
double result2 = calc.Add(5.5, 3.2); // Calls Add(double, double)
int result3 = calc.Add(5, 3, 2); // Calls Add(int, int, int)
string result4 = calc.Add("Hello", "World"); // Calls Add(string, string)
b) Runtime Polymorphism (Method Overriding):
// Base class
public abstract class Shape
{
public abstract double CalculateArea();
public abstract double CalculatePerimeter();
public virtual void Display()
{
Console.WriteLine($"Area: {CalculateArea()}");
Console.WriteLine($"Perimeter: {CalculatePerimeter()}");
}
}
// Derived classes with different implementations
public class Circle : Shape
{
public double Radius { get; set; }
public Circle(double radius)
{
Radius = radius;
}
public override double CalculateArea()
{
return Math.PI * Radius * Radius;
}
public override double CalculatePerimeter()
{
return 2 * Math.PI * Radius;
}
public override void Display()
{
Console.WriteLine($"Circle with radius {Radius}");
base.Display();
}
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public Rectangle(double width, double height)
{
Width = width;
Height = height;
}
public override double CalculateArea()
{
return Width * Height;
}
public override double CalculatePerimeter()
{
return 2 * (Width + Height);
}
public override void Display()
{
Console.WriteLine($"Rectangle {Width}x{Height}");
base.Display();
}
}
public class Triangle : Shape
{
public double Base { get; set; }
public double Height { get; set; }
public double SideA { get; set; }
public double SideB { get; set; }
public double SideC { get; set; }
public Triangle(double baseLength, double height, double sideA, double sideB, double sideC)
{
Base = baseLength;
Height = height;
SideA = sideA;
SideB = sideB;
SideC = sideC;
}
public override double CalculateArea()
{
return (Base * Height) / 2;
}
public override double CalculatePerimeter()
{
return SideA + SideB + SideC;
}
}
// Polymorphic behavior - same interface, different implementations
public class ShapeProcessor
{
public void ProcessShapes(List<Shape> shapes)
{
double totalArea = 0;
foreach (Shape shape in shapes)
{
// Polymorphism in action!
// Calls appropriate CalculateArea() based on actual type
totalArea += shape.CalculateArea();
shape.Display();
Console.WriteLine("---");
}
Console.WriteLine($"Total area of all shapes: {totalArea:F2}");
}
}
// Usage
List<Shape> shapes = new List<Shape>
{
new Circle(5),
new Rectangle(4, 6),
new Triangle(3, 4, 3, 4, 5)
};
ShapeProcessor processor = new ShapeProcessor();
processor.ProcessShapes(shapes); // Each shape behaves differently!
c) Interface Polymorphism:
public interface INotificationSender
{
void Send(string message, string recipient);
}
public class EmailSender : INotificationSender
{
public void Send(string message, string recipient)
{
Console.WriteLine($"Sending email to {recipient}: {message}");
// Email sending logic
}
}
public class SmsSender : INotificationSender
{
public void Send(string message, string recipient)
{
Console.WriteLine($"Sending SMS to {recipient}: {message}");
// SMS sending logic
}
}
public class PushNotificationSender : INotificationSender
{
public void Send(string message, string recipient)
{
Console.WriteLine($"Sending push notification to {recipient}: {message}");
// Push notification logic
}
}
// Notification service using polymorphism
public class NotificationService
{
private readonly List<INotificationSender> senders;
public NotificationService()
{
senders = new List<INotificationSender>
{
new EmailSender(),
new SmsSender(),
new PushNotificationSender()
};
}
public void NotifyAll(string message, string recipient)
{
// Polymorphism - each sender implements Send() differently
foreach (var sender in senders)
{
sender.Send(message, recipient);
}
}
}
// Usage
NotificationService service = new NotificationService();
service.NotifyAll("Your order has shipped!", "user@example.com");
Benefits:
- Flexibility and extensibility
- Code reusability
- Easier maintenance
- Loose coupling
- Supports Open/Closed Principle
Summary of Four Pillars:
| Pillar | What it does | Key benefit |
|---|---|---|
| Encapsulation | Bundles data and methods, hides internals | Data protection, controlled access |
| Abstraction | Hides complexity, shows only essentials | Simplicity, reduces complexity |
| Inheritance | Reuses code from parent classes | Code reusability, hierarchy |
| Polymorphism | Same interface, different implementations | Flexibility, extensibility |
Real-world complete example combining all four:
// Encapsulation & Abstraction
public abstract class Employee
{
// Encapsulation - private fields
private string name;
private decimal baseSalary;
// Public properties
public string Name
{
get => name;
set => name = value ?? throw new ArgumentNullException(nameof(value));
}
protected decimal BaseSalary
{
get => baseSalary;
set => baseSalary = value > 0 ? value : throw new ArgumentException("Salary must be positive");
}
// Abstraction - abstract method
public abstract decimal CalculateSalary();
public virtual void DisplayInfo()
{
Console.WriteLine($"Employee: {Name}");
Console.WriteLine($"Salary: ${CalculateSalary():F2}");
}
}
// Inheritance
public class FullTimeEmployee : Employee
{
public decimal Bonus { get; set; }
public FullTimeEmployee(string name, decimal baseSalary, decimal bonus)
{
Name = name;
BaseSalary = baseSalary;
Bonus = bonus;
}
// Polymorphism - override
public override decimal CalculateSalary()
{
return BaseSalary + Bonus;
}
}
public class ContractEmployee : Employee
{
public int HoursWorked { get; set; }
public decimal HourlyRate { get; set; }
public ContractEmployee(string name, int hours, decimal rate)
{
Name = name;
HoursWorked = hours;
HourlyRate = rate;
BaseSalary = 0;
}
// Polymorphism - different implementation
public override decimal CalculateSalary()
{
return HoursWorked * HourlyRate;
}
public override void DisplayInfo()
{
base.DisplayInfo();
Console.WriteLine($"Hours: {HoursWorked}, Rate: ${HourlyRate}/hr");
}
}
// Polymorphic usage
List<Employee> employees = new List<Employee>
{
new FullTimeEmployee("John Doe", 5000, 1000),
new ContractEmployee("Jane Smith", 160, 50)
};
foreach (Employee emp in employees)
{
emp.DisplayInfo(); // Polymorphic call
Console.WriteLine("---");
}
Related Resources
SOLID is an acronym for five design principles that make software designs more understandable, flexible, and maintainable.
S - Single Responsibility Principle (SRP)
A class should have only one reason to change, meaning it should have only one job or responsibility.
Bad Example (Violates SRP):
// This class has multiple responsibilities
public class User
{
public string Name { get; set; }
public string Email { get; set; }
// Responsibility 1: User data validation
public bool ValidateEmail()
{
return Email.Contains("@");
}
// Responsibility 2: Database operations
public void SaveToDatabase()
{
// Database save logic
Console.WriteLine("Saving to database...");
}
// Responsibility 3: Email notifications
public void SendWelcomeEmail()
{
// Email sending logic
Console.WriteLine($"Sending email to {Email}");
}
// Responsibility 4: Report generation
public string GenerateReport()
{
return $"User Report: {Name}";
}
}
Good Example (Follows SRP):
// Each class has a single responsibility
public class User
{
public string Name { get; set; }
public string Email { get; set; }
}
public class UserValidator
{
public bool ValidateEmail(User user)
{
return !string.IsNullOrEmpty(user.Email) && user.Email.Contains("@");
}
public bool ValidateName(User user)
{
return !string.IsNullOrEmpty(user.Name);
}
}
public class UserRepository
{
public void Save(User user)
{
// Database save logic
Console.WriteLine($"Saving {user.Name} to database...");
}
public User GetById(int id)
{
// Database retrieval logic
return new User();
}
}
public class EmailService
{
public void SendWelcomeEmail(User user)
{
Console.WriteLine($"Sending welcome email to {user.Email}");
}
}
public class UserReportGenerator
{
public string GenerateReport(User user)
{
return $"User Report: {user.Name} ({user.Email})";
}
}
// Usage
User user = new User { Name = "John", Email = "john@example.com" };
UserValidator validator = new UserValidator();
if (validator.ValidateEmail(user))
{
UserRepository repo = new UserRepository();
repo.Save(user);
EmailService emailService = new EmailService();
emailService.SendWelcomeEmail(user);
}
O - Open/Closed Principle (OCP)
Software entities should be open for extension but closed for modification. You should be able to add new functionality without changing existing code.
Bad Example (Violates OCP):
public class PaymentProcessor
{
public void ProcessPayment(string paymentType, decimal amount)
{
if (paymentType == "CreditCard")
{
Console.WriteLine($"Processing credit card payment: ${amount}");
}
else if (paymentType == "PayPal")
{
Console.WriteLine($"Processing PayPal payment: ${amount}");
}
else if (paymentType == "Crypto")
{
// Need to modify existing code to add new payment type!
Console.WriteLine($"Processing crypto payment: ${amount}");
}
// Adding new payment type requires modifying this method
}
}
Good Example (Follows OCP):
// Abstraction - closed for modification
public interface IPaymentMethod
{
void ProcessPayment(decimal amount);
}
// Open for extension - add new payment methods without changing existing code
public class CreditCardPayment : IPaymentMethod
{
public void ProcessPayment(decimal amount)
{
Console.WriteLine($"Processing credit card payment: ${amount}");
}
}
public class PayPalPayment : IPaymentMethod
{
public void ProcessPayment(decimal amount)
{
Console.WriteLine($"Processing PayPal payment: ${amount}");
}
}
public class CryptoPayment : IPaymentMethod
{
public void ProcessPayment(decimal amount)
{
Console.WriteLine($"Processing crypto payment: ${amount}");
}
}
// Can add new payment methods like ApplePay without modifying existing code
public class ApplePayPayment : IPaymentMethod
{
public void ProcessPayment(decimal amount)
{
Console.WriteLine($"Processing Apple Pay payment: ${amount}");
}
}
// Processor doesn't need to change when adding new payment types
public class PaymentProcessor
{
public void Process(IPaymentMethod paymentMethod, decimal amount)
{
paymentMethod.ProcessPayment(amount);
}
}
// Usage
PaymentProcessor processor = new PaymentProcessor();
processor.Process(new CreditCardPayment(), 100);
processor.Process(new PayPalPayment(), 50);
processor.Process(new CryptoPayment(), 200);
processor.Process(new ApplePayPayment(), 75); // New payment type, no changes needed!
L - Liskov Substitution Principle (LSP)
Objects of a superclass should be replaceable with objects of its subclasses without breaking the application. Derived classes must be substitutable for their base classes.
Bad Example (Violates LSP):
public class Bird
{
public virtual void Fly()
{
Console.WriteLine("Flying...");
}
}
public class Sparrow : Bird
{
public override void Fly()
{
Console.WriteLine("Sparrow flying");
}
}
public class Penguin : Bird
{
public override void Fly()
{
// Penguins can't fly! This violates LSP
throw new NotImplementedException("Penguins can't fly!");
}
}
// Usage - breaks LSP
void MakeBirdFly(Bird bird)
{
bird.Fly(); // Throws exception if bird is a Penguin!
}
Good Example (Follows LSP):
// Better abstraction
public abstract class Bird
{
public abstract void Move();
}
public interface IFlyable
{
void Fly();
}
public interface ISwimmable
{
void Swim();
}
public class Sparrow : Bird, IFlyable
{
public override void Move()
{
Fly();
}
public void Fly()
{
Console.WriteLine("Sparrow flying");
}
}
public class Penguin : Bird, ISwimmable
{
public override void Move()
{
Swim();
}
public void Swim()
{
Console.WriteLine("Penguin swimming");
}
}
public class Duck : Bird, IFlyable, ISwimmable
{
public override void Move()
{
Fly();
}
public void Fly()
{
Console.WriteLine("Duck flying");
}
public void Swim()
{
Console.WriteLine("Duck swimming");
}
}
// Usage - respects LSP
void MakeBirdMove(Bird bird)
{
bird.Move(); // Works for all birds!
}
void MakeFlyableFly(IFlyable flyable)
{
flyable.Fly(); // Only called on birds that can fly
}
I - Interface Segregation Principle (ISP)
Clients should not be forced to depend on interfaces they don't use. Many specific interfaces are better than one general-purpose interface.
Bad Example (Violates ISP):
// Fat interface - forces implementations to implement methods they don't need
public interface IWorker
{
void Work();
void Eat();
void Sleep();
void GetPaid();
}
public class HumanWorker : IWorker
{
public void Work()
{
Console.WriteLine("Human working");
}
public void Eat()
{
Console.WriteLine("Human eating");
}
public void Sleep()
{
Console.WriteLine("Human sleeping");
}
public void GetPaid()
{
Console.WriteLine("Human getting paid");
}
}
public class RobotWorker : IWorker
{
public void Work()
{
Console.WriteLine("Robot working");
}
// Robots don't eat or sleep!
public void Eat()
{
throw new NotImplementedException(); // Forced to implement
}
public void Sleep()
{
throw new NotImplementedException(); // Forced to implement
}
public void GetPaid()
{
throw new NotImplementedException(); // Robots don't get paid
}
}
Good Example (Follows ISP):
// Segregated interfaces - clients only depend on what they need
public interface IWorkable
{
void Work();
}
public interface IEatable
{
void Eat();
}
public interface ISleepable
{
void Sleep();
}
public interface IPayable
{
void GetPaid();
}
public class HumanWorker : IWorkable, IEatable, ISleepable, IPayable
{
public void Work()
{
Console.WriteLine("Human working");
}
public void Eat()
{
Console.WriteLine("Human eating");
}
public void Sleep()
{
Console.WriteLine("Human sleeping");
}
public void GetPaid()
{
Console.WriteLine("Human getting paid");
}
}
public class RobotWorker : IWorkable
{
public void Work()
{
Console.WriteLine("Robot working");
}
// Only implements what it needs!
}
public class ContractWorker : IWorkable, IPayable
{
public void Work()
{
Console.WriteLine("Contractor working");
}
public void GetPaid()
{
Console.WriteLine("Contractor getting paid");
}
// Doesn't need Eat or Sleep
}
// Usage
void ManageWorkable(IWorkable worker)
{
worker.Work(); // Can work with any workable, don't care about other behaviors
}
D - Dependency Inversion Principle (DIP)
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.
Bad Example (Violates DIP):
// Low-level module
public class EmailService
{
public void SendEmail(string message)
{
Console.WriteLine($"Sending email: {message}");
}
}
// High-level module depends on low-level module directly
public class UserService
{
private EmailService emailService; // Tight coupling!
public UserService()
{
emailService = new EmailService(); // Creates concrete instance
}
public void RegisterUser(string username)
{
// Registration logic
emailService.SendEmail($"Welcome {username}");
// If we want to use SMS instead, we need to modify this class!
}
}
Good Example (Follows DIP):
// Abstraction
public interface IMessageService
{
void SendMessage(string message);
}
// Low-level modules implement abstraction
public class EmailService : IMessageService
{
public void SendMessage(string message)
{
Console.WriteLine($"Sending email: {message}");
}
}
public class SmsService : IMessageService
{
public void SendMessage(string message)
{
Console.WriteLine($"Sending SMS: {message}");
}
}
public class PushNotificationService : IMessageService
{
public void SendMessage(string message)
{
Console.WriteLine($"Sending push notification: {message}");
}
}
// High-level module depends on abstraction
public class UserService
{
private readonly IMessageService messageService;
// Dependency injected through constructor
public UserService(IMessageService messageService)
{
this.messageService = messageService;
}
public void RegisterUser(string username)
{
// Registration logic
messageService.SendMessage($"Welcome {username}");
// Works with any IMessageService implementation!
}
}
// Usage with Dependency Injection
IMessageService emailService = new EmailService();
UserService userService1 = new UserService(emailService);
userService1.RegisterUser("John");
IMessageService smsService = new SmsService();
UserService userService2 = new UserService(smsService);
userService2.RegisterUser("Jane");
// Easy to switch implementations without modifying UserService
Complete Example Demonstrating All SOLID Principles:
// S - Single Responsibility
public class Order
{
public int OrderId { get; set; }
public List<OrderItem> Items { get; set; }
public decimal TotalAmount { get; set; }
}
// S - Single Responsibility for validation
public class OrderValidator
{
public bool Validate(Order order)
{
return order != null && order.Items?.Count > 0;
}
}
// O & D - Open for extension, depends on abstraction
public interface IOrderRepository
{
void Save(Order order);
}
public interface IPaymentProcessor
{
bool ProcessPayment(decimal amount);
}
public interface INotificationService
{
void Notify(string message);
}
// O - Closed for modification, open for extension
public class SqlOrderRepository : IOrderRepository
{
public void Save(Order order)
{
Console.WriteLine("Saving order to SQL database");
}
}
public class MongoOrderRepository : IOrderRepository
{
public void Save(Order order)
{
Console.WriteLine("Saving order to MongoDB");
}
}
// I - Interface Segregation - specific interfaces
public class StripePaymentProcessor : IPaymentProcessor
{
public bool ProcessPayment(decimal amount)
{
Console.WriteLine($"Processing ${amount} via Stripe");
return true;
}
}
public class EmailNotificationService : INotificationService
{
public void Notify(string message)
{
Console.WriteLine($"Email notification: {message}");
}
}
// D - High-level module depends on abstractions
public class OrderService
{
private readonly IOrderRepository repository;
private readonly IPaymentProcessor paymentProcessor;
private readonly INotificationService notificationService;
private readonly OrderValidator validator;
// D - Dependencies injected
public OrderService(
IOrderRepository repository,
IPaymentProcessor paymentProcessor,
INotificationService notificationService)
{
this.repository = repository;
this.paymentProcessor = paymentProcessor;
this.notificationService = notificationService;
this.validator = new OrderValidator(); // S - Single responsibility
}
public bool PlaceOrder(Order order)
{
// S - Single responsibility for each step
if (!validator.Validate(order))
return false;
if (!paymentProcessor.ProcessPayment(order.TotalAmount))
return false;
repository.Save(order);
notificationService.Notify($"Order {order.OrderId} placed successfully");
return true;
}
}
// Usage - easy to test and extend
var orderService = new OrderService(
new SqlOrderRepository(),
new StripePaymentProcessor(),
new EmailNotificationService()
);
Order order = new Order
{
OrderId = 1,
Items = new List<OrderItem>(),
TotalAmount = 99.99m
};
orderService.PlaceOrder(order);
Benefits of SOLID:
- Easier to maintain and extend
- More testable code
- Reduced coupling
- Better code organization
- Easier to understand
- Facilitates refactoring
Related Resources
Method Overloading occurs when multiple methods in the same class have the same name but different parameters (different number, type, or order of parameters). It's a compile-time polymorphism.
Method Overriding occurs when a derived class provides a specific implementation of a method that is already defined in its base class. It's a runtime polymorphism.
Example:
// Method Overloading
public class Calculator
{
// Same method name, different parameters
public int Add(int a, int b)
{
return a + b;
}
public int Add(int a, int b, int c)
{
return a + b + c;
}
public double Add(double a, double b)
{
return a + b;
}
}
// Method Overriding
public class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("Some generic animal sound");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Woof!");
}
}
public class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine("Meow!");
}
}
// Usage
var calc = new Calculator();
calc.Add(5, 10); // Calls first method
calc.Add(5, 10, 15); // Calls second method
Animal myDog = new Dog();
myDog.MakeSound(); // Outputs: Woof!
Related Resources
Polymorphism means "many forms" and allows objects to be treated as instances of their parent class while exhibiting behavior specific to their actual class. There are two types:
- Compile-time Polymorphism (Static) - Method Overloading, Operator Overloading
- Runtime Polymorphism (Dynamic) - Method Overriding
Example:
// Runtime Polymorphism Example
public abstract class Shape
{
public abstract double CalculateArea();
}
public class Circle : Shape
{
public double Radius { get; set; }
public Circle(double radius)
{
Radius = radius;
}
public override double CalculateArea()
{
return Math.PI * Radius * Radius;
}
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public Rectangle(double width, double height)
{
Width = width;
Height = height;
}
public override double CalculateArea()
{
return Width * Height;
}
}
public class Triangle : Shape
{
public double Base { get; set; }
public double Height { get; set; }
public Triangle(double baseLength, double height)
{
Base = baseLength;
Height = height;
}
public override double CalculateArea()
{
return 0.5 * Base * Height;
}
}
// Usage - Single interface, multiple forms
List<Shape> shapes = new List<Shape>
{
new Circle(5),
new Rectangle(4, 6),
new Triangle(3, 8)
};
foreach (Shape shape in shapes)
{
Console.WriteLine($"Area: {shape.CalculateArea()}");
// Each shape calculates area differently (polymorphic behavior)
}
Related Resources
Sealed Classes are classes that cannot be inherited. They prevent other classes from deriving from them.
Sealed Methods are overridden methods that cannot be overridden further in derived classes.
Example:
// Sealed Class Example
public sealed class FinalClass
{
public void Display()
{
Console.WriteLine("This class cannot be inherited");
}
}
// This will cause a compilation error
// public class DerivedClass : FinalClass { } // ERROR!
// Sealed Method Example
public class BaseClass
{
public virtual void Print()
{
Console.WriteLine("Base Print");
}
}
public class MiddleClass : BaseClass
{
public sealed override void Print()
{
Console.WriteLine("Middle Print - This cannot be overridden further");
}
}
public class DerivedClass : MiddleClass
{
// This will cause a compilation error
// public override void Print() { } // ERROR! Cannot override sealed method
}
// Real-world example: String class in .NET is sealed
// public sealed class String { ... }