https://github.com/rashedul-alam46/solid-principles
SOLID Principles in C#
https://github.com/rashedul-alam46/solid-principles
csharp oop solid-principles
Last synced: 15 days ago
JSON representation
SOLID Principles in C#
- Host: GitHub
- URL: https://github.com/rashedul-alam46/solid-principles
- Owner: rashedul-alam46
- Created: 2025-08-22T13:21:57.000Z (11 months ago)
- Default Branch: main
- Last Pushed: 2025-09-19T15:49:53.000Z (10 months ago)
- Last Synced: 2026-04-01T09:31:57.304Z (3 months ago)
- Topics: csharp, oop, solid-principles
- Language: C#
- Homepage:
- Size: 99.6 KB
- Stars: 4
- Watchers: 0
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
SOLID Principales
---
# π³ SOLID Principles in C# β Payment Processing Example
This project demonstrates how to apply the **SOLID principles** in a simple **Payment Processing System** using C#.
The goal is to show how following SOLID makes code **cleaner, maintainable, and extensible**.
---
## π Overview
The system processes orders using different payment methods and generates invoices.
It applies all **five SOLID principles**:
1. **Single Responsibility Principle (SRP)**
Each class has one responsibility.
- `OrderRepository` β Saves orders
- `PdfInvoice` / `EmailInvoice` β Generate invoices
- `CheckoutService` β Manages checkout process
2. **Open/Closed Principle (OCP)**
The system is open for extension but closed for modification.
- New payment methods (e.g., `CreditCardPayment`, `PayPalPayment`, `BitcoinPayment`) can be added without changing existing code.
3. **Liskov Substitution Principle (LSP)**
Subclasses can replace their parent classes without breaking behavior.
- Any `IPaymentProcessor` (Credit Card, PayPal, Bitcoin) can be used in `CheckoutService`.
4. **Interface Segregation Principle (ISP)**
Clients are not forced to implement methods they donβt use.
- `IInvoiceGenerator` is separate from `IPaymentProcessor`.
5. **Dependency Inversion Principle (DIP)**
High-level modules depend on abstractions, not concrete implementations.
- `CheckoutService` depends on `IPaymentProcessor` and `IInvoiceGenerator` interfaces.
---
## S - Single Responsibility Principle
```csharp
// Each class has only one reason to change.
public class Order
{
public string OrderId { get; set; }
public decimal Amount { get; set; }
}
public class OrderRepository
{
public void Save(Order order)
{
Console.WriteLine($"Order {order.OrderId} saved to database.");
}
}
```
## O - Open/Closed Principle
```csharp
// Add new payment methods without modifying existing logic.
public interface IPaymentProcessor
{
void ProcessPayment(Order order);
}
public class CreditCardPayment : IPaymentProcessor
{
public void ProcessPayment(Order order)
{
Console.WriteLine($"Processing Credit Card payment for {order.Amount}");
}
}
public class PayPalPayment : IPaymentProcessor
{
public void ProcessPayment(Order order)
{
Console.WriteLine($"Processing PayPal payment for {order.Amount}");
}
}
```
## L - Liskov Substitution Principle
```csharp
// Subtypes (payment methods) can replace the parent type (IPaymentProcessor).
public class BitcoinPayment : IPaymentProcessor
{
public void ProcessPayment(Order order)
{
Console.WriteLine($"Processing Bitcoin payment for {order.Amount}");
}
}
```
## I - Interface Segregation Principle
```csh
// Instead of one big interface, we separate responsibilities.
public interface IInvoiceGenerator
{
void GenerateInvoice(Order order);
}
public class PdfInvoice : IInvoiceGenerator
{
public void GenerateInvoice(Order order)
{
Console.WriteLine($"PDF invoice generated for order {order.OrderId}");
}
}
public class EmailInvoice : IInvoiceGenerator
{
public void GenerateInvoice(Order order)
{
Console.WriteLine($"Invoice emailed for order {order.OrderId}");
}
}
```
## D - Dependency Inversion Principle
```csharp
// High-level modules depend on abstractions, not concretions.
public class CheckoutService
{
private readonly IPaymentProcessor _paymentProcessor;
private readonly IInvoiceGenerator _invoiceGenerator;
private readonly OrderRepository _orderRepository;
public CheckoutService(IPaymentProcessor paymentProcessor, IInvoiceGenerator invoiceGenerator, OrderRepository orderRepository)
{
_paymentProcessor = paymentProcessor;
_invoiceGenerator = invoiceGenerator;
_orderRepository = orderRepository;
}
public void Checkout(Order order)
{
_paymentProcessor.ProcessPayment(order);
_invoiceGenerator.GenerateInvoice(order);
_orderRepository.Save(order);
}
}
```
## π Example Run
```csharp
Order order = new Order { OrderId = "123", Amount = 100.50m };
// Choose payment and invoice types
IPaymentProcessor payment = new PayPalPayment();
IInvoiceGenerator invoice = new PdfInvoice();
// Inject dependencies into CheckoutService
CheckoutService checkout = new CheckoutService(payment, invoice, new OrderRepository());
// Process checkout
checkout.Checkout(order);
```
## Output
```csharp
Processing PayPal payment for 100.50
PDF invoice generated for order 123
Order 123 saved to database.
```
## β
How SOLID is Applied Here:
- SRP β OrderRepository only saves orders, CheckoutService only manages checkout, Invoice classes only handle invoices.
- OCP β We can add new payments (e.g., StripePayment) without changing existing classes.
- LSP β Any payment type (CreditCard, PayPal, Bitcoin) can replace IPaymentProcessor without breaking behavior.
- ISP β Invoices are split (PdfInvoice, EmailInvoice), not forced into one big interface.
- DIP β CheckoutService depends on abstractions (IPaymentProcessor, IInvoiceGenerator) instead of concrete classes.
This way, the system is flexible, testable, and extendable.
## π΄ Breaking OCP (not open for extension, but modification)
```csharp
public class PaymentProcessor
{
public void ProcessPayment(Order order, string paymentType)
{
if (paymentType == "CreditCard")
{
Console.WriteLine($"Processing Credit Card payment for {order.Amount}");
}
else if (paymentType == "PayPal")
{
Console.WriteLine($"Processing PayPal payment for {order.Amount}");
}
else if (paymentType == "Bitcoin")
{
Console.WriteLine($"Processing Bitcoin payment for {order.Amount}");
}
// β Every time we add a new payment type (Stripe, ApplePay),
// we must MODIFY this class β violates OCP
}
}
```
Problem:
- Adding StripePayment means we must edit PaymentProcessor.
- The class keeps growing and becomes harder to maintain.
## π΄ Breaking LSP Example
High chance of introducing bugs while modifying.
```csharp
public interface IPaymentProcessor
{
void ProcessPayment(Order order);
}
```
Now, imagine we create a GiftCardPayment class but force it to implement ProcessPayment, even though gift cards donβt work like normal payments:
```csharp
public class GiftCardPayment : IPaymentProcessor
{
public void ProcessPayment(Order order)
{
// β GiftCard canβt really process payments like CreditCard/PayPal
// So we throw an exception
throw new NotSupportedException("GiftCard cannot process payment directly.");
}
}
public void Checkout(Order order)
{
_paymentProcessor.ProcessPayment(order); // β Will crash if GiftCardPayment is used
}
```
Why this breaks LSP?
- LSP rule: Subclasses (or implementations) must be usable anywhere the base type is expected.
- Here, if we substitute GiftCardPayment for IPaymentProcessor, it throws an exception instead of behaving properly.
- Client code (CheckoutService) now has to know special cases β violates LSP.
β
Fixing LSP
Instead of forcing GiftCardPayment into the wrong interface, we restructure abstractions:
```csharp
public interface IPaymentProcessor
{
void ProcessPayment(Order order);
}
public interface IGiftCardRedemption
{
void RedeemGiftCard(Order order);
}
public class CreditCardPayment : IPaymentProcessor
{
public void ProcessPayment(Order order)
{
Console.WriteLine($"Processing Credit Card payment for {order.Amount}");
}
}
public class PayPalPayment : IPaymentProcessor
{
public void ProcessPayment(Order order)
{
Console.WriteLine($"Processing PayPal payment for {order.Amount}");
}
}
public class GiftCardRedemption : IGiftCardRedemption
{
public void RedeemGiftCard(Order order)
{
Console.WriteLine($"Redeeming gift card for {order.Amount}");
}
}
```
β
Why this respects LSP?
- Each implementation behaves correctly without throwing exceptions.
- GiftCardRedemption is no longer pretending to be a normal payment processor.
- Substitution works: anywhere you expect IPaymentProcessor, you can safely use CreditCardPayment or PayPalPayment without breaking behavior.
## π΄ Breaking ISP
Suppose we design one fat interface that tries to handle every possible invoice format:
```csharp
public interface IInvoiceGenerator
{
void GeneratePdfInvoice(Order order);
void GenerateEmailInvoice(Order order);
void GenerateExcelInvoice(Order order);
}
```
Now, classes are forced to implement methods they donβt need:
```csharp
public class PdfInvoiceGenerator : IInvoiceGenerator
{
public void GeneratePdfInvoice(Order order)
{
Console.WriteLine($"PDF invoice generated for order {order.OrderId}");
}
public void GenerateEmailInvoice(Order order)
{
// β Not applicable β forced to implement
throw new NotImplementedException();
}
public void GenerateExcelInvoice(Order order)
{
// β Not applicable β forced to implement
throw new NotImplementedException();
}
}
```
π¨ Why this breaks ISP?
- Classes should not be forced to depend on methods they donβt use.
- Here, PdfInvoiceGenerator only cares about PDF, but is forced to implement email and Excel too.
- Any change in IInvoiceGenerator impacts all implementations unnecessarily.
- β
Fixing ISP
Split the interface into smaller, more specific interfaces:
```csharp
public interface IPdfInvoiceGenerator
{
void GeneratePdfInvoice(Order order);
}
public interface IEmailInvoiceGenerator
{
void GenerateEmailInvoice(Order order);
}
public interface IExcelInvoiceGenerator
{
void GenerateExcelInvoice(Order order);
}
```
Now, each implementation only does what it should:
```csharp
public class PdfInvoiceGenerator : IPdfInvoiceGenerator
{
public void GeneratePdfInvoice(Order order)
{
Console.WriteLine($"PDF invoice generated for order {order.OrderId}");
}
}
public class EmailInvoiceGenerator : IEmailInvoiceGenerator
{
public void GenerateEmailInvoice(Order order)
{
Console.WriteLine($"Invoice emailed for order {order.OrderId}");
}
}
```
β
Why this respects ISP?
- Each class depends only on what it really needs.
- No unnecessary throw new NotImplementedException().
- Easier to maintain and extend.
## π΄ Breaking DIP
CheckoutService does depend on abstractions (IPaymentProcessor, IInvoiceGenerator), which is good (follows DIP).
If it did not depend on abstractions, it would look like this (bad example β):
```csharp
public class CheckoutService
{
private readonly CreditCardPayment _creditCardPayment;
private readonly PdfInvoice _pdfInvoice;
private readonly OrderRepository _orderRepository;
public CheckoutService()
{
// Directly instantiating concrete classes (bad practice)
_creditCardPayment = new CreditCardPayment();
_pdfInvoice = new PdfInvoice();
_orderRepository = new OrderRepository();
}
public void Checkout(Order order)
{
_creditCardPayment.ProcessPayment(order);
_pdfInvoice.GenerateInvoice(order);
_orderRepository.Save(order);
}
}
```
π¨ Whatβs wrong here?
- Tight coupling β CheckoutService is locked to CreditCardPayment and PdfInvoice.
- No flexibility β If we want to switch to PayPalPayment or EmailInvoice, we must modify CheckoutService.
- Hard to test β Canβt mock dependencies easily in unit tests.
β
Why this version is better:
- Depends on interfaces (abstractions).
- Easy to swap different implementations without modifying CheckoutService.
- Easy to test with mocks.
```csharp
public CheckoutService(IPaymentProcessor paymentProcessor, IInvoiceGenerator invoiceGenerator, OrderRepository orderRepository)
```
So the difference is:
- Without DIP β CheckoutService creates and depends on concrete classes.
- With DIP β CheckoutService depends on interfaces/abstractions provided from outside (injected).