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:
-
Creational
-
Structural
-
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.