Md Mominul Islam | Software and Data Enginnering | SQL Server, .NET, Power BI, Azure Blog

while(!(succeed=try()));

LinkedIn Portfolio Banner

Latest

Home Top Ad

Responsive Ads Here

Post Top Ad

Responsive Ads Here

Friday, August 22, 2025

Module:10 - Mastering Software Design Patterns: A Comprehensive Course Outline for .NET and Java (Beginner to Advanced)

 


Table of Contents

  1. Introduction to Software Design Patterns

    • 1.1 What Are Design Patterns?

    • 1.2 Importance in .NET and Java

    • 1.3 Course Objectives and Structure

  2. Foundational Concepts

    • 2.1 Object-Oriented Programming (OOP) Principles

    • 2.2 SOLID Principles in Depth

    • 2.3 UML and Design Pattern Visualization

  3. Creational Design Patterns

    • 3.1 Singleton Pattern

    • 3.2 Factory Method Pattern

    • 3.3 Abstract Factory Pattern

    • 3.4 Builder Pattern

    • 3.5 Prototype Pattern

  4. Structural Design Patterns

    • 4.1 Adapter Pattern

    • 4.2 Decorator Pattern

    • 4.3 Facade Pattern

    • 4.4 Composite Pattern

    • 4.5 Proxy Pattern

  5. Behavioral Design Patterns

    • 5.1 Observer Pattern

    • 5.2 Strategy Pattern

    • 5.3 Command Pattern

    • 5.4 Chain of Responsibility Pattern

    • 5.5 State Pattern

  6. Intermediate Patterns in Action

    • 6.1 Repository Pattern

    • 6.2 Unit of Work Pattern

    • 6.3 Dependency Injection

  7. Modern .NET and Java Features in Design Patterns

    • 7.1 Records in .NET

    • 7.2 Sealed Classes in Java

    • 7.3 Pattern Matching in .NET and Java

  8. Asynchronous and Reactive Design Patterns

    • 8.1 Async/Await in .NET

    • 8.2 Reactive Streams in Java

    • 8.3 Task Parallel Library (TPL) in .NET

  9. Cloud-Native Design Patterns

    • 9.1 Circuit Breaker Pattern

    • 9.2 Bulkhead Pattern

    • 9.3 Retry Pattern

  10. Event-Driven and Streaming Design Patterns

    • 10.1 Event Sourcing

    • 10.2 CQRS (Command Query Responsibility Segregation)

    • 10.3 Publish-Subscribe Pattern

  11. CI/CD and DevOps Integration

    • 11.1 Design Patterns in CI/CD Pipelines

    • 11.2 DevOps Best Practices for Pattern Implementation

  12. Real-World Case Study: E-Commerce Platform

    • 12.1 Applying Patterns in an ASP.NET Core Application

    • 12.2 Database Integration with SQL Server

    • 12.3 Exception Handling and Logging

  13. Best Practices, Pros, Cons, and Alternatives

    • 13.1 Best Practices for Design Pattern Implementation

    • 13.2 Pros and Cons of Each Pattern

    • 13.3 Alternatives to Traditional Patterns

  14. Conclusion and Next Steps

    • 14.1 Recap of Key Learnings

    • 14.2 Resources for Further Learning


1. Introduction to Software Design Patterns

1.1 What Are Design Patterns?

Design patterns are reusable solutions to common software design problems, acting as blueprints for structuring code effectively. Originating from the "Gang of Four" (GoF) book, Design Patterns: Elements of Reusable Object-Oriented Software, they provide proven templates for solving issues in object creation, structure, and behavior.

1.2 Importance in .NET and Java

In .NET and Java, design patterns enhance code maintainability, scalability, and readability. They address challenges like object instantiation, system decoupling, and efficient communication, making them essential for building robust enterprise applications.

1.3 Course Objectives and Structure

This course guides beginners to advanced developers through design patterns in .NET and Java, focusing on modern features like async/await, cloud-native, and event-driven architectures. It includes practical C# and ASP.NET examples with SQL Server integration, emphasizing best practices and real-world applications.


2. Foundational Concepts

2.1 Object-Oriented Programming (OOP) Principles

OOP principles—encapsulation, inheritance, polymorphism, and abstraction—are the foundation of design patterns. Understanding these ensures effective pattern implementation.

2.2 SOLID Principles in Depth

The SOLID principles (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) guide clean code design. For example:

  • Single Responsibility Principle (SRP): A class should have one reason to change.

  • Example: Separating user authentication from user profile management.

2.3 UML and Design Pattern Visualization

Unified Modeling Language (UML) diagrams, like class and sequence diagrams, visualize pattern structures. Tools like StarUML help developers design and communicate patterns effectively.


3. Creational Design Patterns

3.1 Singleton Pattern

Purpose: Ensures a class has only one instance and provides global access. Real-Life Example: A logging service in an ASP.NET application. C# Example:

public sealed class Logger
{
    private static readonly Lazy<Logger> instance = new Lazy<Logger>(() => new Logger());
    private Logger() { }

    public static Logger Instance => instance.Value;

    public void Log(string message)
    {
        Console.WriteLine($"Log: {message}");
    }
}

Pros: Controlled access, thread-safe with Lazy. Cons: Can lead to tight coupling; difficult to unit test. Alternatives: Dependency Injection for loose coupling.

3.2 Factory Method Pattern

Purpose: Defines an interface for creating objects, letting subclasses decide the class to instantiate. Real-Life Example: Creating payment processors (CreditCard, PayPal) in an e-commerce system. C# Example:

public interface IPaymentProcessor
{
    void ProcessPayment(decimal amount);
}

public class CreditCardProcessor : IPaymentProcessor
{
    public void ProcessPayment(decimal amount) => Console.WriteLine($"Processed {amount} via Credit Card");
}

public class PayPalProcessor : IPaymentProcessor
{
    public void ProcessPayment(decimal amount) => Console.WriteLine($"Processed {amount} via PayPal");
}

public abstract class PaymentFactory
{
    public abstract IPaymentProcessor CreateProcessor();
}

public class CreditCardFactory : PaymentFactory
{
    public override IPaymentProcessor CreateProcessor() => new CreditCardProcessor();
}

Pros: Promotes loose coupling; supports Open/Closed principle. Cons: Increased complexity with additional classes. Alternatives: Abstract Factory for families of objects.

3.3 Abstract Factory Pattern

Purpose: Creates families of related objects without specifying their concrete classes. Real-Life Example: UI theme factories (Dark, Light) for a web application. C# Example:

public interface IButton { void Render(); }
public interface ITextBox { void Render(); }

public class DarkButton : IButton { public void Render() => Console.WriteLine("Dark Button"); }
public class LightButton : IButton { public void Render() => Console.WriteLine("Light Button"); }

public class DarkTextBox : ITextBox { public void Render() => Console.WriteLine("Dark TextBox"); }
public class LightTextBox : ITextBox { public void Render() => Console.WriteLine("Light TextBox"); }

public interface IUIFactory
{
    IButton CreateButton();
    ITextBox CreateTextBox();
}

public class DarkUIFactory : IUIFactory
{
    public IButton CreateButton() => new DarkButton();
    public ITextBox CreateTextBox() => new DarkTextBox();
}

public class LightUIFactory : IUIFactory
{
    public IButton CreateButton() => new LightButton();
    public ITextBox CreateTextBox() => new LightTextBox();
}

Pros: Ensures consistency among related objects. Cons: Complex to implement and extend. Alternatives: Factory Method for simpler scenarios.

3.4 Builder Pattern

Purpose: Constructs complex objects step-by-step. Real-Life Example: Building a complex order object in an e-commerce system. C# Example:

public class Order
{
    public string CustomerName { get; set; }
    public List<string> Items { get; set; }
    public decimal TotalAmount { get; set; }
}

public class OrderBuilder
{
    private Order _order = new Order();

    public OrderBuilder SetCustomer(string name)
    {
        _order.CustomerName = name;
        return this;
    }

    public OrderBuilder AddItem(string item)
    {
        _order.Items ??= new List<string>();
        _order.Items.Add(item);
        return this;
    }

    public OrderBuilder SetTotal(decimal amount)
    {
        _order.TotalAmount = amount;
        return this;
    }

    public Order Build() => _order;
}

// Usage
var order = new OrderBuilder()
    .SetCustomer("John Doe")
    .AddItem("Laptop")
    .AddItem("Mouse")
    .SetTotal(1200.50m)
    .Build();

Pros: Improves readability; supports immutability. Cons: Requires additional classes; can be verbose. Alternatives: Factory pattern for simpler object creation.

3.5 Prototype Pattern

Purpose: Creates new objects by copying an existing object. Real-Life Example: Cloning a product template in a catalog. C# Example:

public class Product : ICloneable
{
    public string Name { get; set; }
    public decimal Price { get; set; }

    public object Clone() => MemberwiseClone();
}

// Usage
var product = new Product { Name = "Laptop", Price = 1000 };
var clonedProduct = (Product)product.Clone();

Pros: Reduces object creation overhead. Cons: Shallow vs. deep copy issues. Alternatives: Factory pattern for controlled instantiation.


4. Structural Design Patterns

4.1 Adapter Pattern

Purpose: Allows incompatible interfaces to work together. Real-Life Example: Integrating a third-party payment API with an internal system. C# Example:

public interface IPaymentGateway
{
    void ExecutePayment(decimal amount);
}

public class ThirdPartyPayment
{
    public void MakePayment(decimal amount) => Console.WriteLine($"Third-party payment: {amount}");
}

public class PaymentAdapter : IPaymentGateway
{
    private readonly ThirdPartyPayment _thirdPartyPayment;

    public PaymentAdapter(ThirdPartyPayment thirdPartyPayment)
    {
        _thirdPartyPayment = thirdPartyPayment;
    }

    public void ExecutePayment(decimal amount) => _thirdPartyPayment.MakePayment(amount);
}

Pros: Enables integration of legacy systems. Cons: Adds complexity with wrapper classes. Alternatives: Facade for simplified interfaces.

4.2 Decorator Pattern

Purpose: Dynamically adds responsibilities to objects. Real-Life Example: Adding logging to a service in ASP.NET. C# Example:

public interface INotificationService
{
    void Send(string message);
}

public class EmailNotification : INotificationService
{
    public void Send(string message) => Console.WriteLine($"Email: {message}");
}

public class LoggingDecorator : INotificationService
{
    private readonly INotificationService _service;

    public LoggingDecorator(INotificationService service)
    {
        _service = service;
    }

    public void Send(string message)
    {
        Console.WriteLine($"Logging: {message}");
        _service.Send(message);
    }
}

Pros: Flexible extension of behavior. Cons: Can lead to many small classes. Alternatives: Strategy pattern for behavior changes.

4.3 Facade Pattern

Purpose: Provides a simplified interface to a complex subsystem. Real-Life Example: Simplifying database operations in an ASP.NET app. C# Example:

public class OrderSubsystem
{
    public void CreateOrder() => Console.WriteLine("Order created");
    public void UpdateInventory() => Console.WriteLine("Inventory updated");
}

public class PaymentSubsystem
{
    public void ProcessPayment() => Console.WriteLine("Payment processed");
}

public class OrderFacade
{
    private readonly OrderSubsystem _orderSubsystem = new();
    private readonly PaymentSubsystem _paymentSubsystem = new();

    public void PlaceOrder()
    {
        _orderSubsystem.CreateOrder();
        _paymentSubsystem.ProcessPayment();
        _orderSubsystem.UpdateInventory();
    }
}

Pros: Simplifies client code. Cons: Can become a god object. Alternatives: Mediator for complex interactions.

4.4 Composite Pattern

Purpose: Treats individual objects and compositions uniformly. Real-Life Example: Building a product category tree in an e-commerce system. C# Example:

public interface IComponent
{
    void Display();
}

public class Product : IComponent
{
    private readonly string _name;
    public Product(string name) => _name = name;
    public void Display() => Console.WriteLine($"Product: {_name}");
}

public class Category : IComponent
{
    private readonly string _name;
    private readonly List<IComponent> _components = new();

    public Category(string name) => _name = name;

    public void Add(IComponent component) => _components.Add(component);
    public void Display() 
    {
        Console.WriteLine($"Category: {_name}");
        foreach (var component in _components) component.Display();
    }
}

Pros: Simplifies hierarchical structures. Cons: Can overly generalize components. Alternatives: Decorator for single-object enhancements.

4.5 Proxy Pattern

Purpose: Controls access to an object, adding functionality like lazy loading. Real-Life Example: Lazy loading of images in a web application. C# Example:

public interface IImage
{
    void Display();
}

public class RealImage : IImage
{
    private readonly string _filename;
    public RealImage(string filename) 
    {
        _filename = filename;
        Console.WriteLine($"Loading {_filename}");
    }

    public void Display() => Console.WriteLine($"Displaying {_filename}");
}

public class ImageProxy : IImage
{
    private readonly string _filename;
    private RealImage _realImage;

    public ImageProxy(string filename) => _filename = filename;

    public void Display()
    {
        _realImage ??= new RealImage(_filename);
        _realImage.Display();
    }
}

Pros: Reduces resource usage. Cons: Adds latency for proxy overhead. Alternatives: Flyweight for shared objects.


5. Behavioral Design Patterns

5.1 Observer Pattern

Purpose: Defines a one-to-many dependency where objects are notified of state changes. Real-Life Example: Notifying users of order status updates. C# Example:

public interface IObserver
{
    void Update(string message);
}

public class Customer : IObserver
{
    private readonly string _name;
    public Customer(string name) => _name = name;
    public void Update(string message) => Console.WriteLine($"{_name} received: {message}");
}

public class OrderSubject
{
    private readonly List<IObserver> _observers = new();
    public void Attach(IObserver observer) => _observers.Add(observer);
    public void Notify(string message)
    {
        foreach (var observer in _observers) observer.Update(message);
    }
}

Pros: Promotes loose coupling. Cons: Can lead to memory leaks if observers aren’t detached. Alternatives: Event-driven frameworks like SignalR.

5.2 Strategy Pattern

Purpose: Defines interchangeable algorithms at runtime. Real-Life Example: Applying different discount strategies in an e-commerce system. C# Example:

public interface IDiscountStrategy
{
    decimal ApplyDiscount(decimal price);
}

public class RegularDiscount : IDiscountStrategy
{
    public decimal ApplyDiscount(decimal price) => price * 0.9m;
}

public class PremiumDiscount : IDiscountStrategy
{
    public decimal ApplyDiscount(decimal price) => price * 0.8m;
}

public class OrderContext
{
    private IDiscountStrategy _strategy;
    public void SetStrategy(IDiscountStrategy strategy) => _strategy = strategy;
    public decimal CalculatePrice(decimal price) => _strategy.ApplyDiscount(price);
}

Pros: Flexible behavior swapping. Cons: Increases number of classes. Alternatives: Template Method for fixed algorithms.

5.3 Command Pattern

Purpose: Encapsulates a request as an object, allowing parameterization and queuing. Real-Life Example: Implementing undoable actions in a shopping cart. C# Example:

public interface ICommand
{
    void Execute();
}

public class AddToCartCommand : ICommand
{
    private readonly ShoppingCart _cart;
    private readonly string _item;

    public AddToCartCommand(ShoppingCart cart, string item)
    {
        _cart = cart;
        _item = item;
    }

    public void Execute() => _cart.AddItem(_item);
}

public class ShoppingCart
{
    private readonly List<string> _items = new();
    public void AddItem(string item) => _items.Add(item);
}

Pros: Supports undo/redo; decouples sender and receiver. Cons: Can lead to class proliferation. Alternatives: Strategy for simpler behavior changes.

5.4 Chain of Responsibility Pattern

Purpose: Passes a request along a chain of handlers. Real-Life Example: Processing payment methods sequentially. C# Example:

public abstract class PaymentHandler
{
    protected PaymentHandler _next;
    public void SetNext(PaymentHandler next) => _next = next;
    public abstract void Handle(decimal amount);
}

public class CreditCardHandler : PaymentHandler
{
    public override void Handle(decimal amount)
    {
        if (amount <= 1000) Console.WriteLine("Paid with Credit Card");
        else _next?.Handle(amount);
    }
}

public class PayPalHandler : PaymentHandler
{
    public override void Handle(decimal amount)
    {
        if (amount <= 5000) Console.WriteLine("Paid with PayPal");
        else _next?.Handle(amount);
    }
}

Pros: Decouples sender from receivers. Cons: Can result in unhandled requests. Alternatives: Observer for notification-based systems.

5.5 State Pattern

Purpose: Allows an object to alter its behavior when its state changes. Real-Life Example: Managing order states (Pending, Shipped, Delivered). C# Example:

public interface IOrderState
{
    void ProcessOrder(OrderContext context);
}

public class PendingState : IOrderState
{
    public void ProcessOrder(OrderContext context)
    {
        Console.WriteLine("Order is pending");
        context.SetState(new ShippedState());
    }
}

public class ShippedState : IOrderState
{
    public void ProcessOrder(OrderContext context)
    {
        Console.WriteLine("Order is shipped");
        context.SetState(new DeliveredState());
    }
}

public class DeliveredState : IOrderState
{
    public void ProcessOrder(OrderContext context) => Console.WriteLine("Order is delivered");
}

public class OrderContext
{
    private IOrderState _state;
    public OrderContext() => _state = new PendingState();
    public void SetState(IOrderState state) => _state = state;
    public void Process() => _state.ProcessOrder(this);
}

Pros: Encapsulates state-specific behavior. Cons: Increases class count. Alternatives: Strategy for simpler behavior changes.


6. Intermediate Patterns in Action

6.1 Repository Pattern

Purpose: Abstracts data access logic, providing a collection-like interface. Real-Life Example: Managing product data in an ASP.NET application. C# Example:

public interface IProductRepository
{
    Task<Product> GetByIdAsync(int id);
    Task AddAsync(Product product);
}

public class ProductRepository : IProductRepository
{
    private readonly ApplicationDbContext _context;

    public ProductRepository(ApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<Product> GetByIdAsync(int id)
    {
        return await _context.Products.FindAsync(id) ?? throw new KeyNotFoundException("Product not found");
    }

    public async Task AddAsync(Product product)
    {
        await _context.Products.AddAsync(product);
        await _context.SaveChangesAsync();
    }
}

Pros: Centralizes data access; improves testability. Cons: Adds abstraction layer. Alternatives: Direct Entity Framework queries.

6.2 Unit of Work Pattern

Purpose: Maintains a list of objects affected by a business transaction and coordinates changes. Real-Life Example: Managing multiple repository operations in a transaction. C# Example:

public interface IUnitOfWork : IDisposable
{
    IProductRepository Products { get; }
    Task<int> CommitAsync();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly ApplicationDbContext _context;
    public IProductRepository Products { get; }

    public UnitOfWork(ApplicationDbContext context)
    {
        _context = context;
        Products = new ProductRepository(context);
    }

    public async Task<int> CommitAsync() => await _context.SaveChangesAsync();
    public void Dispose() => _context.Dispose();
}

Pros: Ensures transactional consistency. Cons: Can be overkill for simple applications. Alternatives: Direct database transactions.

6.3 Dependency Injection

Purpose: Implements Inversion of Control for dependency management. Real-Life Example: Injecting services in an ASP.NET Core controller. C# Example:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<IProductRepository, ProductRepository>();
        services.AddScoped<IUnitOfWork, UnitOfWork>();
        services.AddDbContext<ApplicationDbContext>();
    }
}

public class ProductController : ControllerBase
{
    private readonly IProductRepository _repository;

    public ProductController(IProductRepository repository)
    {
        _repository = repository;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Get(int id)
    {
        try
        {
            var product = await _repository.GetByIdAsync(id);
            return Ok(product);
        }
        catch (KeyNotFoundException ex)
        {
            return NotFound(ex.Message);
        }
    }
}

Pros: Enhances testability and modularity. Cons: Increases setup complexity. Alternatives: Service Locator pattern (less preferred).


7. Modern .NET and Java Features in Design Patterns

7.1 Records in .NET

Purpose: Immutable data structures for simplified data handling. Real-Life Example: Representing a product in an e-commerce system. C# Example:

public record Product(int Id, string Name, decimal Price);

// Usage
var product = new Product(1, "Laptop", 1000);
var productCopy = product with { Price = 1100 }; // Creates a new instance with modified price

Pros: Concise syntax; built-in immutability. Cons: Limited to data-centric scenarios. Alternatives: Classes with manual immutability.

7.2 Sealed Classes in Java

Purpose: Restricts class inheritance for better control. Java Example:

public sealed interface PaymentService permits CreditCardService, PayPalService {
    void processPayment(double amount);
}

public final class CreditCardService implements PaymentService {
    public void processPayment(double amount) {
        System.out.println("Processed " + amount + " via Credit Card");
    }
}

public final class PayPalService implements PaymentService {
    public void processPayment(double amount) {
        System.out.println("Processed " + amount + " via PayPal");
    }
}

Pros: Enhances security and maintainability. Cons: Limits extensibility. Alternatives: Final classes or composition.

7.3 Pattern Matching in .NET and Java

Purpose: Simplifies type checking and data extraction. C# Example:

public static string GetPaymentDescription(IPaymentProcessor processor) => processor switch
{
    CreditCardProcessor => "Credit Card Payment",
    PayPalProcessor => "PayPal Payment",
    _ => throw new NotSupportedException("Unknown payment processor")
};

Java Example:

public static String getPaymentDescription(PaymentService service) {
    return switch (service) {
        case CreditCardService c -> "Credit Card Payment";
        case PayPalService p -> "PayPal Payment";
        default -> throw new IllegalArgumentException("Unknown payment service");
    };
}

Pros: Reduces boilerplate code; improves readability. Cons: Limited to supported types. Alternatives: Traditional if-else checks.


8. Asynchronous and Reactive Design Patterns

8.1 Async/Await in .NET

Purpose: Simplifies asynchronous programming. Real-Life Example: Fetching product data asynchronously in ASP.NET. C# Example:

public class ProductService
{
    private readonly IProductRepository _repository;

    public ProductService(IProductRepository repository)
    {
        _repository = repository;
    }

    public async Task<Product> GetProductAsync(int id)
    {
        try
        {
            return await _repository.GetByIdAsync(id);
        }
        catch (Exception ex)
        {
            throw new ApplicationException("Failed to fetch product", ex);
        }
    }
}

Pros: Improves responsiveness; simplifies async code. Cons: Can lead to deadlocks if misused. Alternatives: Callback-based async programming.

8.2 Reactive Streams in Java

Purpose: Handles asynchronous data streams with backpressure. Real-Life Example: Processing real-time order updates. Java Example:

import java.util.concurrent.Flow.*;

public class OrderPublisher implements Publisher<String> {
    private List<Subscriber<? super String>> subscribers = new ArrayList<>();

    @Override
    public void subscribe(Subscriber<? super String> subscriber) {
        subscribers.add(subscriber);
        subscriber.onSubscribe(new Subscription() {
            @Override
            public void request(long n) {
                // Simulate sending order updates
                subscriber.onNext("Order updated");
                subscriber.onComplete();
            }

            @Override
            public void cancel() {}
        });
    }
}

Pros: Handles large data streams efficiently. Cons: Steep learning curve. Alternatives: Observer pattern for simpler scenarios.

8.3 Task Parallel Library (TPL) in .NET

Purpose: Simplifies parallel and concurrent programming. Real-Life Example: Processing multiple orders concurrently. C# Example:

public async Task ProcessOrdersAsync(List<int> orderIds)
{
    var tasks = orderIds.Select(id => Task.Run(async () =>
    {
        try
        {
            var order = await _repository.GetByIdAsync(id);
            Console.WriteLine($"Processed order {id}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error processing order {id}: {ex.Message}");
        }
    }));

    await Task.WhenAll(tasks);
}

Pros: Enhances performance for CPU-bound tasks. Cons: Requires careful resource management. Alternatives: Parallel.For for simpler loops.


9. Cloud-Native Design Patterns

9.1 Circuit Breaker Pattern

Purpose: Prevents cascading failures in distributed systems. Real-Life Example: Handling API failures in an ASP.NET microservice. C# Example (Using Polly):

public class PaymentService
{
    private readonly HttpClient _client;
    private readonly AsyncCircuitBreakerPolicy _circuitBreaker = Policy
        .Handle<HttpRequestException>()
        .CircuitBreakerAsync(2, TimeSpan.FromSeconds(30));

    public PaymentService(HttpClient client)
    {
        _client = client;
    }

    public async Task ProcessPaymentAsync(decimal amount)
    {
        await _circuitBreaker.ExecuteAsync(async () =>
        {
            var response = await _client.PostAsync("api/payment", null);
            response.EnsureSuccessStatusCode();
        });
    }
}

Pros: Enhances system resilience. Cons: Requires configuration tuning. Alternatives: Retry pattern for simpler scenarios.

9.2 Bulkhead Pattern

Purpose: Isolates resources to prevent system-wide failures. Real-Life Example: Limiting concurrent API calls in a microservice. C# Example:

public class OrderService
{
    private readonly SemaphoreSlim _semaphore = new(5); // Limit to 5 concurrent calls

    public async Task ProcessOrderAsync(int orderId)
    {
        await _semaphore.WaitAsync();
        try
        {
            // Process order
            await Task.Delay(1000); // Simulate work
            Console.WriteLine($"Processed order {orderId}");
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

Pros: Prevents resource exhaustion. Cons: Adds complexity to resource management. Alternatives: Circuit Breaker for failure handling.

9.3 Retry Pattern

Purpose: Automatically retries failed operations. Real-Life Example: Retrying database connections in ASP.NET. C# Example (Using Polly):

public class DatabaseService
{
    private readonly AsyncRetryPolicy _retryPolicy = Policy
        .Handle<SqlException>()
        .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

    public async Task ExecuteQueryAsync(string query)
    {
        await _retryPolicy.ExecuteAsync(async () =>
        {
            using var connection = new SqlConnection("connection_string");
            await connection.OpenAsync();
            // Execute query
        });
    }
}

Pros: Improves reliability for transient failures. Cons: Can mask underlying issues. Alternatives: Circuit Breaker for persistent failures.


10. Event-Driven and Streaming Design Patterns

10.1 Event Sourcing

Purpose: Stores state as a sequence of events. Real-Life Example: Tracking order history in an e-commerce system. C# Example:

public class OrderEvent
{
    public string EventType { get; set; }
    public string Data { get; set; }
}

public class OrderAggregate
{
    private readonly List<OrderEvent> _events = new();

    public void ApplyEvent(OrderEvent evt)
    {
        _events.Add(evt);
        // Apply event to update state
    }

    public IEnumerable<OrderEvent> GetEvents() => _events;
}

Pros: Provides audit trail; supports state reconstruction. Cons: Complex to implement and query. Alternatives: Traditional CRUD for simpler scenarios.

10.2 CQRS (Command Query Responsibility Segregation)

Purpose: Separates read and write operations for scalability. Real-Life Example: Separating order creation from order queries. C# Example:

public class OrderCommand
{
    public int OrderId { get; set; }
    public string CustomerName { get; set; }
}

public class OrderQuery
{
    public int OrderId { get; set; }
    public string Status { get; set; }
}

public class OrderCommandHandler
{
    private readonly IUnitOfWork _unitOfWork;

    public OrderCommandHandler(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public async Task HandleAsync(OrderCommand command)
    {
        var order = new Order { CustomerName = command.CustomerName };
        await _unitOfWork.Products.AddAsync(order);
        await _unitOfWork.CommitAsync();
    }
}

public class OrderQueryHandler
{
    private readonly IProductRepository _repository;

    public OrderQueryHandler(IProductRepository repository)
    {
        _repository = repository;
    }

    public async Task<OrderQuery> HandleAsync(int orderId)
    {
        var order = await _repository.GetByIdAsync(orderId);
        return new OrderQuery { OrderId = order.Id, Status = "Pending" };
    }
}

Pros: Optimizes read/write performance. Cons: Increases system complexity. Alternatives: Simple CRUD for smaller systems.

10.3 Publish-Subscribe Pattern

Purpose: Decouples event producers from consumers. Real-Life Example: Notifying services of order updates using Azure Service Bus. C# Example:

public class OrderPublisher
{
    private readonly ITopicClient _topicClient;

    public OrderPublisher(ITopicClient topicClient)
    {
        _topicClient = topicClient;
    }

    public async Task PublishAsync(string message)
    {
        var msg = new Message(Encoding.UTF8.GetBytes(message));
        await _topicClient.SendAsync(msg);
    }
}

public class OrderSubscriber
{
    private readonly ISubscriptionClient _subscriptionClient;

    public OrderSubscriber(ISubscriptionClient subscriptionClient)
    {
        _subscriptionClient = subscriptionClient;
        _subscriptionClient.RegisterMessageHandler(
            async (message, token) =>
            {
                var content = Encoding.UTF8.GetString(message.Body);
                Console.WriteLine($"Received: {content}");
                await _subscriptionClient.CompleteAsync(message.SystemProperties.LockToken);
            },
            new MessageHandlerOptions(ExceptionReceivedHandler) { MaxConcurrentCalls = 1 });
    }

    private Task ExceptionReceivedHandler(ExceptionReceivedEventArgs args)
    {
        Console.WriteLine($"Error: {args.Exception.Message}");
        return Task.CompletedTask;
    }
}

Pros: Scalable event distribution. Cons: Requires message broker setup. Alternatives: Observer pattern for in-memory events.


11. CI/CD and DevOps Integration

11.1 Design Patterns in CI/CD Pipelines

Purpose: Ensures patterns align with automated build and deployment processes. Real-Life Example: Automating deployment of an ASP.NET microservice. C# Example (Azure DevOps YAML):

stages:
- stage: Build
  jobs:
  - job: BuildJob
    steps:
    - task: DotNetCoreCLI@2
      inputs:
        command: 'build'
        projects: '**/*.csproj'
- stage: Deploy
  jobs:
  - job: DeployJob
    steps:
    - task: AzureWebApp@1
      inputs:
        azureSubscription: 'MySubscription'
        appName: 'MyApp'
        package: '$(Build.ArtifactStagingDirectory)/**/*.zip'

Pros: Streamlines deployment; ensures consistency. Cons: Requires CI/CD tool expertise. Alternatives: Manual deployments (less reliable).

11.2 DevOps Best Practices for Pattern Implementation

  • Containerization: Use Docker to encapsulate pattern-based services.

  • Monitoring: Implement logging with Serilog for pattern diagnostics.

  • Testing: Integrate unit tests for patterns in CI/CD pipelines.


12. Real-World Case Study: E-Commerce Platform

12.1 Applying Patterns in an ASP.NET Core Application

An e-commerce platform uses multiple patterns:

  • Repository and Unit of Work: Manage product and order data.

  • Factory: Create payment processors.

  • Observer: Notify users of order updates.

  • Circuit Breaker: Handle payment API failures.

12.2 Database Integration with SQL Server

C# Example (Entity Framework Core):

public class ApplicationDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    public DbSet<Order> Orders { get; set; }

    public ApplicationDbContext(DbContextOptions options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>().HasKey(p => p.Id);
        modelBuilder.Entity<Order>().HasKey(o => o.Id);
    }
}

SQL Server Schema:

CREATE TABLE Products (
    Id INT PRIMARY KEY IDENTITY,
    Name NVARCHAR(100),
    Price DECIMAL(18,2)
);

CREATE TABLE Orders (
    Id INT PRIMARY KEY IDENTITY,
    CustomerName NVARCHAR(100),
    TotalAmount DECIMAL(18,2)
);

12.3 Exception Handling and Logging

C# Example (Using Serilog):

public class ProductController : ControllerBase
{
    private readonly ILogger<ProductController> _logger;
    private readonly IProductRepository _repository;

    public ProductController(ILogger<ProductController> logger, IProductRepository repository)
    {
        _logger = logger;
        _repository = repository;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Get(int id)
    {
        try
        {
            var product = await _repository.GetByIdAsync(id);
            _logger.LogInformation("Fetched product {Id}", id);
            return Ok(product);
        }
        catch (KeyNotFoundException ex)
        {
            _logger.LogError(ex, "Product {Id} not found", id);
            return NotFound(ex.Message);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Unexpected error fetching product {Id}", id);
            return StatusCode(500, "Internal server error");
        }
}

Pros: Centralized error handling; detailed logging. Cons: Requires careful logging configuration. Alternatives: Built-in ASP.NET logging.


13. Best Practices, Pros, Cons, and Alternatives

13.1 Best Practices for Design Pattern Implementation

  • Follow SOLID Principles: Ensure maintainable and scalable code.

  • Use Dependency Injection: Enhance testability and modularity.

  • Implement Exception Handling: Use try-catch blocks and logging.

  • Document Patterns: Use UML diagrams and comments for clarity.

13.2 Pros and Cons of Each Pattern

  • Singleton: Easy global access but risks tight coupling.

  • Factory: Promotes extensibility but adds complexity.

  • Observer: Decouples components but requires careful observer management.

13.3 Alternatives to Traditional Patterns

  • Functional Programming: Use immutable data and pure functions.

  • Microservices: Replace monolithic patterns with distributed systems.

  • Serverless: Leverage cloud functions for event-driven tasks.


14. Conclusion and Next Steps

14.1 Recap of Key Learnings

This course covered design patterns from foundational to advanced, focusing on .NET and Java implementations. Key takeaways include:

  • Understanding GoF patterns and their applications.

  • Leveraging modern features like records and async/await.

  • Building resilient cloud-native and event-driven systems.

14.2 Resources for Further Learning

  • Books: Design Patterns by GoF, Head First Design Patterns.

  • Online Courses: Udemy, Coursera, ScholarHat.

  • Tools: Visual Studio, StarUML, Azure DevOps.


This comprehensive guide equips you to apply design patterns effectively in .NET and Java, building robust, scalable applications with modern features and best practices.

No comments:

Post a Comment

Thanks for your valuable comment...........
Md. Mominul Islam

Post Bottom Ad

Responsive Ads Here