C sharp - Design Patterns in C#

Design Patterns are proven solutions to common software design problems.
They are not code templates, but architectural strategies.

In C#, design patterns help in:

  • Writing maintainable code

  • Reducing coupling

  • Improving scalability

  • Making systems extensible

They are generally classified into three categories:

  1. Creational

  2. Structural

  3. Behavioral


1. Creational Patterns (Object Creation Control)

These deal with how objects are created.


a) Singleton Pattern

Ensures only one instance of a class exists.

class Logger
{
    private static Logger _instance;
    private static readonly object _lock = new object();

    private Logger() { }

    public static Logger Instance
    {
        get
        {
            lock (_lock)
            {
                return _instance ??= new Logger();
            }
        }
    }
}

Use cases:

  • Logging

  • Configuration

  • Caching systems

Problem:
Can make testing difficult if overused.


b) Factory Pattern

Creates objects without exposing creation logic.

interface IShape
{
    void Draw();
}

class Circle : IShape
{
    public void Draw() => Console.WriteLine("Circle");
}

class ShapeFactory
{
    public static IShape Create(string type)
    {
        return type switch
        {
            "circle" => new Circle(),
            _ => throw new ArgumentException()
        };
    }
}

Use when:

  • Object creation logic is complex

  • You want loose coupling


2. Structural Patterns (Class & Object Composition)

These focus on organizing relationships.


a) Adapter Pattern

Allows incompatible interfaces to work together.

Example:
You have a legacy class:

class OldPrinter
{
    public void PrintText(string text) { }
}

But system expects:

interface IPrinter
{
    void Print(string message);
}

Adapter:

class PrinterAdapter : IPrinter
{
    private OldPrinter _oldPrinter;

    public PrinterAdapter(OldPrinter oldPrinter)
    {
        _oldPrinter = oldPrinter;
    }

    public void Print(string message)
    {
        _oldPrinter.PrintText(message);
    }
}

Use case:

  • Integrating third-party libraries

  • Legacy system integration


b) Decorator Pattern

Adds behavior dynamically without modifying original class.

Example:
Adding logging to service:

class LoggingService : IService
{
    private readonly IService _service;

    public LoggingService(IService service)
    {
        _service = service;
    }

    public void Execute()
    {
        Console.WriteLine("Before");
        _service.Execute();
        Console.WriteLine("After");
    }
}

Used in:

  • Middleware pipelines

  • Stream classes in .NET


3. Behavioral Patterns (Communication Between Objects)


a) Observer Pattern

One-to-many dependency.

In C#, events implement Observer pattern.

Publisher → Notifies subscribers.

Used in:

  • UI systems

  • Event-driven architectures


b) Strategy Pattern

Encapsulates interchangeable behaviors.

interface IPaymentStrategy
{
    void Pay();
}

class CreditCardPayment : IPaymentStrategy
{
    public void Pay() => Console.WriteLine("Paid via Card");
}

Usage:

class PaymentContext
{
    private IPaymentStrategy _strategy;

    public PaymentContext(IPaymentStrategy strategy)
    {
        _strategy = strategy;
    }

    public void Execute() => _strategy.Pay();
}

Use when:

  • Multiple algorithms exist

  • You want runtime selection


c) Command Pattern

Encapsulates a request as an object.

Used in:

  • Undo/Redo systems

  • Task scheduling

  • Queues


4. Why Design Patterns Matter in C#

Large applications need:

  • Loose coupling

  • High cohesion

  • Extensibility

  • Maintainability

Patterns enforce SOLID principles.


5. Commonly Used Patterns in .NET Ecosystem

  • Dependency Injection (built into ASP.NET Core)

  • Repository Pattern

  • Unit of Work

  • Mediator (CQRS systems)

  • Builder Pattern

  • Facade Pattern


6. Anti-Pattern Warning

Overusing patterns can:

  • Overcomplicate code

  • Reduce readability

  • Increase abstraction unnecessarily

Use patterns when a real design problem exists.


7. Real-World Example (ASP.NET Core)

ASP.NET Core internally uses:

  • Dependency Injection (Factory + Strategy)

  • Middleware pipeline (Decorator)

  • Logging providers (Strategy)

  • Events (Observer)

Modern enterprise C# heavily depends on patterns.