Design Patterns & Practices  

What Are Design Patterns in .NET With Real-World Examples?

Design patterns in .NET are proven, reusable solutions to common software design problems. They provide structured approaches for organizing code, improving maintainability, enhancing scalability, and promoting best practices in enterprise application development. In C# and ASP.NET Core applications, design patterns help developers build loosely coupled, testable, and extensible systems that align with modern architectural standards.

Design patterns are typically categorized into three major groups: Creational, Structural, and Behavioral patterns.

Creational Design Patterns in .NET

Creational patterns focus on object creation mechanisms, ensuring flexibility and reuse.

1. Singleton Pattern

The Singleton pattern ensures that a class has only one instance and provides a global point of access to it.

Real-world example in .NET:

  • Logging service

  • Configuration manager

  • Caching service

Example:

public sealed class Logger
{
    private static readonly Logger _instance = new Logger();

    private Logger() { }

    public static Logger Instance => _instance;

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

In ASP.NET Core, dependency injection often replaces manual Singleton implementations.

2. Factory Pattern

The Factory pattern creates objects without exposing the instantiation logic to the client.

Real-world example:

  • Payment gateway selection (CreditCard, UPI, PayPal)

  • Notification services (Email, SMS, Push)

Example:

public interface IPayment
{
    void Process();
}

public class CreditCardPayment : IPayment
{
    public void Process() => Console.WriteLine("Processing credit card payment");
}

public class PaymentFactory
{
    public static IPayment Create(string type)
    {
        return type switch
        {
            "CreditCard" => new CreditCardPayment(),
            _ => throw new ArgumentException("Invalid type")
        };
    }
}

Factories improve extensibility and reduce tight coupling.

3. Dependency Injection Pattern

Dependency Injection (DI) promotes loose coupling by injecting dependencies instead of creating them directly.

Real-world example:

  • Injecting repository into service

  • Injecting logger into controller

ASP.NET Core has built-in support for dependency injection through the IServiceCollection container.

Structural Design Patterns in .NET

Structural patterns focus on organizing classes and objects to form larger structures.

4. Repository Pattern

The Repository pattern abstracts data access logic from business logic.

Real-world example:

  • Separating Entity Framework Core queries from service layer

Example:

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

public class ProductRepository : IProductRepository
{
    private readonly AppDbContext _context;

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

    public async Task<Product> GetByIdAsync(int id)
    {
        return await _context.Products.FindAsync(id);
    }
}

This improves testability and maintainability.

5. Adapter Pattern

The Adapter pattern allows incompatible interfaces to work together.

Real-world example:

  • Integrating third-party payment API with internal payment interface

It acts as a bridge between two incompatible systems.

Behavioral Design Patterns in .NET

Behavioral patterns focus on communication between objects and responsibility distribution.

6. Strategy Pattern

The Strategy pattern defines a family of algorithms and allows switching between them at runtime.

Real-world example:

  • Different discount calculation strategies

  • Different tax calculation rules

Example:

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

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

This improves flexibility and eliminates conditional logic.

7. Observer Pattern

The Observer pattern defines a one-to-many relationship where changes in one object notify dependent objects.

Real-world example:

  • Event-driven systems

  • Real-time notifications

  • Messaging systems

In .NET, events and delegates are built-in implementations of the Observer pattern.

8. Mediator Pattern

The Mediator pattern reduces direct communication between objects and centralizes interaction logic.

Real-world example:

  • Using MediatR in ASP.NET Core to implement CQRS

This improves separation of concerns and simplifies complex interactions.

Why Design Patterns Matter in Enterprise .NET Applications

Design patterns:

  • Improve code readability

  • Promote reusable architecture

  • Reduce tight coupling

  • Support scalability

  • Simplify testing

Modern .NET development, especially in ASP.NET Core Web API and microservices architecture, heavily relies on patterns such as Repository, Dependency Injection, Strategy, and Mediator.

Common Mistakes When Using Design Patterns

  • Overusing patterns unnecessarily

  • Applying patterns without understanding the problem

  • Creating excessive abstraction for simple applications

  • Ignoring performance implications

Patterns should solve real problems, not introduce unnecessary complexity.

Summary

Design patterns in .NET are structured, reusable solutions that address common software design challenges across object creation, system structure, and behavior coordination. By applying creational patterns like Singleton and Factory, structural patterns such as Repository and Adapter, and behavioral patterns including Strategy, Observer, and Mediator, developers can build scalable, maintainable, and loosely coupled enterprise applications. When used appropriately, design patterns enhance code quality, promote architectural consistency, and support long-term evolution of modern .NET systems.