An open API service indexing awesome lists of open source software.

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#

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).