Advanced C# Design Patterns: Scalable Architecture, Complex Patterns, and Real-World Applications

Advanced C# Design Patterns: Scalable Architecture, Complex Patterns, and Real-World Applications

Advanced design patterns focus on application architecture, scalability, and maintainability at scale. These patterns are commonly used in enterprise systems and often work together rather than in isolation.

At this level, the goal is not just solving isolated problems, but designing entire systems that are robust, testable, and adaptable.

Selected Advanced Design Patterns

We’ll cover:

• Dependency Injection (DI)
• Mediator Pattern
• CQRS (Command Query Responsibility Segregation)
• Unit of Work Pattern

1. Dependency Injection (DI)

Purpose: Provides dependencies to a class from the outside, rather than creating them internally.

When to Use

• Large applications
• Testable systems
• Loosely coupled architecture

Example

public interface ILogger
{
    void Log(string message);
}

public class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
}

public class OrderService
{
    private readonly ILogger _logger;

    public OrderService(ILogger logger)
    {
        _logger = logger;
    }

    public void ProcessOrder()
    {
        _logger.Log("Order processed");
    }
}

Usage

ILogger logger = new ConsoleLogger();
var service = new OrderService(logger);

service.ProcessOrder();

Advantages of Dependency Injection

• Improves testability (easy mocking)
• Promotes loose coupling
• Centralized configuration of dependencies

Alternatives of Dependency Injection

• Service Locator (less transparent)
• Manual instantiation (tightly coupled)

2. Mediator Pattern

Purpose: Reduces direct communication between objects by introducing a mediator that handles interactions.

When to Use

• Complex object communication
• Reducing dependencies between components

Example

public interface IMediator
{
    void Send(string message, Colleague colleague);
}

public abstract class Colleague
{
    protected IMediator _mediator;

    protected Colleague(IMediator mediator)
    {
        _mediator = mediator;
    }
}

public class ConcreteMediator : IMediator
{
    public Colleague1 Colleague1 { get; set; }
    public Colleague2 Colleague2 { get; set; }

    public void Send(string message, Colleague colleague)
    {
        if (colleague == Colleague1)
            Colleague2.Receive(message);
        else
            Colleague1.Receive(message);
    }
}

public class Colleague1 : Colleague
{
    public Colleague1(IMediator mediator) : base(mediator) { }

    public void Send(string message)
    {
        _mediator.Send(message, this);
    }

    public void Receive(string message)
    {
        Console.WriteLine("Colleague1 received: " + message);
    }
}

public class Colleague2 : Colleague
{
    public Colleague2(IMediator mediator) : base(mediator) { }

    public void Send(string message)
    {
        _mediator.Send(message, this);
    }

    public void Receive(string message)
    {
        Console.WriteLine("Colleague2 received: " + message);
    }
}

Usage

var mediator = new ConcreteMediator();

var c1 = new Colleague1(mediator);
var c2 = new Colleague2(mediator);

mediator.Colleague1 = c1;
mediator.Colleague2 = c2;

c1.Send("Hello from C1");
c2.Send("Hello from C2");

Advantages of Mediator Pattern

• Reduces coupling between classes
• Centralizes communication logic
• Easier to maintain complex systems

Alternatives of Mediator Pattern

• Observer Pattern (event-driven communication)
• Direct references (simpler but tightly coupled)

3. CQRS (Command Query Responsibility Segregation)

Purpose: Separates read operations (queries) from write operations (commands).

When to Use

• High-performance systems
• Complex domains
• Systems requiring scalability

Example

// Command
public class CreateOrderCommand
{
    public string ProductName { get; set; }
}

// Query
public class GetOrdersQuery { }

// Command Handler
public class OrderCommandHandler
{
    private readonly List _orders = new List();

    public void Handle(CreateOrderCommand command)
    {
        _orders.Add(command.ProductName);
    }

    public List GetOrders()
    {
        return _orders;
    }
}

Usage

var handler = new OrderCommandHandler();

handler.Handle(new CreateOrderCommand { ProductName = "Laptop" });

var orders = handler.GetOrders();

Advantages of CQRS

• Improves scalability
• Separates concerns clearly
• Optimizes read/write independently

Alternatives of CQRS

• CRUD-based architecture (simpler systems)
• Layered architecture without separation

4. Unit of Work Pattern

Purpose: Maintains a list of operations and commits them as a single transaction.

When to Use

• Database transactions
• Multiple related operations

Example

public class UnitOfWork
{
    private readonly List _operations = new List();

    public void Register(string operation)
    {
        _operations.Add(operation);
    }

    public void Commit()
    {
        foreach (var op in _operations)
        {
            Console.WriteLine($"Executing: {op}");
        }

        _operations.Clear();
    }
}

Usage

var uow = new UnitOfWork();

uow.Register("Insert Order");
uow.Register("Update Inventory");

uow.Commit();

Advantages of Unit of Work

• Ensures transactional consistency
• Groups related operations
• Reduces database calls

Alternatives of Unit of Work

• Direct transaction handling
• ORM built-in transaction management

Summary Table

Pattern Purpose Best Use Case Alternative
Dependency Injection Externalize dependencies Testable architectures Service Locator
Mediator Centralized communication Complex object interactions Observer
CQRS Separate read/write logic High-scale systems CRUD
Unit of Work Transaction management Database operations ORM transactions

Closing Thoughts

Advanced patterns are less about isolated techniques and more about how systems are structured as a whole. In real-world applications, these patterns are often combined:

• DI + Mediator (common in modern frameworks)
• CQRS + Unit of Work (data-heavy systems)

Understanding when not to use these patterns is just as important as knowing how to implement them. Overengineering can introduce unnecessary complexity, so always align pattern choice with actual requirements.

Contents related to 'Advanced C# Design Patterns: Scalable Architecture, Complex Patterns, and Real-World Applications'

C# Design Patterns for Beginners: Concepts, Benefits, and Practical Examples
C# Design Patterns for Beginners: Concepts, Benefits, and Practical Examples
C# Design Patterns (Intermediate): Practical Patterns, Use Cases, and Alternatives
C# Design Patterns (Intermediate): Practical Patterns, Use Cases, and Alternatives