C# code example that fully adheres to the SOLID principles and incorporates multiple design patterns requires a well-structured and somewhat complex system. Below is an example of a simple e-commerce system where we demonstrate the SOLID principles and apply various design patterns.
- Single Responsibility Principle (SRP): Each class should have only one responsibility.
- Open/Closed Principle (OCP): Classes should be open for extension but closed for modification.
- Liskov Substitution Principle (LSP): Subtypes must be substitutable for their base types.
- Interface Segregation Principle (ISP): Clients should not be forced to depend on interfaces they do not use.
- Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules but on abstractions.
Factory Pattern: For creating product instances.
Strategy Pattern: For applying different discount strategies.
Observer Pattern: For notifying users about order status changes.
Decorator Pattern: For adding features to products dynamically.
Repository Pattern: For data access abstraction.
Dependency Injection: For managing dependencies between classes.
using System;
using System.Collections.Generic;
// Product Class (SRP, Decorator Pattern)
public abstract class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
public Product(string name, decimal price)
{
Name = name;
Price = price;
}
public abstract decimal GetPrice();
}
// Concrete Products
public class BasicProduct : Product
{
public BasicProduct(string name, decimal price) : base(name, price) { }
public override decimal GetPrice() => Price;
}
// Decorator Pattern for Product
public abstract class ProductDecorator : Product
{
protected Product _product;
public ProductDecorator(Product product) : base(product.Name, product.Price)
{
_product = product;
}
public override decimal GetPrice() => _product.GetPrice();
}
public class GiftWrapDecorator : ProductDecorator
{
public GiftWrapDecorator(Product product) : base(product) { }
public override decimal GetPrice() => _product.GetPrice() + 5.00m;
}
// Strategy Pattern for Discounts
public interface IDiscountStrategy
{
decimal ApplyDiscount(decimal price);
}
public class NoDiscountStrategy : IDiscountStrategy
{
public decimal ApplyDiscount(decimal price) => price;
}
public class BlackFridayDiscountStrategy : IDiscountStrategy
{
public decimal ApplyDiscount(decimal price) => price * 0.8m;
}
// Factory Pattern for Product Creation
public class ProductFactory
{
public static Product CreateProduct(string productName, decimal price, bool giftWrap = false)
{
Product product = new BasicProduct(productName, price);
if (giftWrap)
product = new GiftWrapDecorator(product);
return product;
}
}
// Observer Pattern for Order Notifications
public interface IOrderObserver
{
void Update(string message);
}
public class Customer : IOrderObserver
{
public string Name { get; set; }
public Customer(string name)
{
Name = name;
}
public void Update(string message)
{
Console.WriteLine($"{Name} received notification: {message}");
}
}
public class Order
{
private List<IOrderObserver> _observers = new List<IOrderObserver>();
public void AddObserver(IOrderObserver observer)
{
_observers.Add(observer);
}
public void RemoveObserver(IOrderObserver observer)
{
_observers.Remove(observer);
}
public void NotifyObservers(string message)
{
foreach (var observer in _observers)
{
observer.Update(message);
}
}
public void ProcessOrder()
{
NotifyObservers("Your order has been processed.");
}
}
// Repository Pattern for Data Access
public interface IProductRepository
{
void AddProduct(Product product);
Product GetProduct(string productName);
}
public class ProductRepository : IProductRepository
{
private Dictionary<string, Product> _products = new Dictionary<string, Product>();
public void AddProduct(Product product)
{
_products[product.Name] = product;
}
public Product GetProduct(string productName)
{
return _products.ContainsKey(productName) ? _products[productName] : null;
}
}
// Dependency Injection for Order Processing
public class OrderService
{
private readonly IProductRepository _productRepository;
private readonly IDiscountStrategy _discountStrategy;
public OrderService(IProductRepository productRepository, IDiscountStrategy discountStrategy)
{
_productRepository = productRepository;
_discountStrategy = discountStrategy;
}
public void PlaceOrder(string productName)
{
var product = _productRepository.GetProduct(productName);
if (product != null)
{
var finalPrice = _discountStrategy.ApplyDiscount(product.GetPrice());
Console.WriteLine($"Order placed for {product.Name} with final price {finalPrice:C}");
}
else
{
Console.WriteLine("Product not found.");
}
}
}
// Main Program
class Program
{
static void Main(string[] args)
{
// Setup
IProductRepository productRepository = new ProductRepository();
IDiscountStrategy discountStrategy = new BlackFridayDiscountStrategy();
OrderService orderService = new OrderService(productRepository, discountStrategy);
// Create Products
Product product1 = ProductFactory.CreateProduct("Laptop", 1000.00m, true);
productRepository.AddProduct(product1);
Product product2 = ProductFactory.CreateProduct("Smartphone", 500.00m);
productRepository.AddProduct(product2);
// Place Orders
orderService.PlaceOrder("Laptop");
// Observer Pattern Usage
Order order = new Order();
Customer customer = new Customer("John Doe");
order.AddObserver(customer);
order.ProcessOrder();
}
}
Single Responsibility Principle (SRP): Each class has a single responsibility, like Product, Order, Customer, etc.
Open/Closed Principle (OCP): New product types, discounts, or observers can be added without modifying existing classes.
Liskov Substitution Principle (LSP): Derived classes like GiftWrapDecorator can be used wherever the base Product class is expected.
Interface Segregation Principle (ISP): Interfaces like IDiscountStrategy, IOrderObserver, and IProductRepository ensure that classes depend only on the methods they need.
Dependency Inversion Principle (DIP): High-level modules like OrderService depend on abstractions (IDiscountStrategy, IProductRepository), not on concrete implementations.
Factory Pattern: Used to create product instances with optional gift wrapping.
Decorator Pattern: Adds functionalities (e.g., gift wrapping) to products dynamically.
Strategy Pattern: Allows switching between different discount strategies.
Observer Pattern: Notifies customers of order status changes.
Repository Pattern: Abstracts data access for products.
Dependency Injection: Injects dependencies like IDiscountStrategy and IProductRepository into OrderService.