Design Patterns in C#
1. Singleton Pattern
---------------------
Intent:
Ensure a class has only one instance and provide a global point of access to it.
Use Case:
Logger, Config Manager, Thread Pools
Code Example:
public sealed class Singleton {
private static Singleton _instance = null;
private static readonly object _lock = new object();
private Singleton() { }
public static Singleton Instance {
get {
lock (_lock) {
if (_instance == null)
_instance = new Singleton();
return _instance;
public void DoWork() {
Console.WriteLine("Singleton doing work");
}
Pros:
- Saves memory with lazy initialization
- Globally accessible instance
Cons:
- Difficult to unit test/mock
- Hidden dependencies
------------------------------------------------------------
2. Factory Method Pattern
--------------------------
Intent:
Define an interface for creating an object but let subclasses decide which class to instantiate.
Use Case:
Creating objects like Shapes, Buttons, Notifications
Code Example:
abstract class Product {
public abstract void Operation();
class ConcreteProductA : Product {
public override void Operation() => Console.WriteLine("Product A");
class ConcreteProductB : Product {
public override void Operation() => Console.WriteLine("Product B");
abstract class Creator {
public abstract Product FactoryMethod();
}
class ConcreteCreatorA : Creator {
public override Product FactoryMethod() => new ConcreteProductA();
class ConcreteCreatorB : Creator {
public override Product FactoryMethod() => new ConcreteProductB();
Pros:
- Decouples object creation from usage
- Easier to introduce new product types
Cons:
- More classes and code to manage
------------------------------------------------------------
3. Abstract Factory Pattern
-----------------------------
Intent:
Provide an interface for creating families of related objects without specifying their concrete classes.
Use Case:
Cross-platform UI components, Themes
Code Example:
interface IButton {
void Paint();
class WinButton : IButton {
public void Paint() => Console.WriteLine("Windows Button");
class MacButton : IButton {
public void Paint() => Console.WriteLine("Mac Button");
interface IGUIFactory {
IButton CreateButton();
class WinFactory : IGUIFactory {
public IButton CreateButton() => new WinButton();
class MacFactory : IGUIFactory {
public IButton CreateButton() => new MacButton();
Pros:
- Supports consistency among products
- Easy to switch families
Cons:
- Difficult to add new product types
------------------------------------------------------------
4. Adapter Pattern
-------------------
Intent:
Convert the interface of a class into another interface the client expects.
Use Case:
Integration with legacy or external APIs
Code Example:
interface ITarget {
void Request();
class Adaptee {
public void SpecificRequest() => Console.WriteLine("SpecificRequest");
class Adapter : ITarget {
private Adaptee _adaptee = new Adaptee();
public void Request() => _adaptee.SpecificRequest();
Pros:
- Reuse existing code
- Improve compatibility
Cons:
- Added layer increases complexity
------------------------------------------------------------
5. Observer Pattern
--------------------
Intent:
One-to-many dependency between objects so when one object changes, others are notified.
Use Case:
Event listeners, Pub/Sub systems
Code Example:
interface IObserver {
void Update(string message);
interface ISubject {
void Attach(IObserver observer);
void Detach(IObserver observer);
void Notify();
class ConcreteSubject : ISubject {
private List<IObserver> observers = new();
private string message;
public void Attach(IObserver observer) => observers.Add(observer);
public void Detach(IObserver observer) => observers.Remove(observer);
public void Notify() {
foreach (var observer in observers)
observer.Update(message);
public void CreateMessage(string msg) {
message = msg;
Notify();
class ConcreteObserver : IObserver {
private string _name;
public ConcreteObserver(string name) => _name = name;
public void Update(string message) => Console.WriteLine($"{_name} received: {message}");
}
Pros:
- Loose coupling
- Dynamic subscriber list
Cons:
- Possible memory leaks
- Hard to debug
------------------------------------------------------------
6. Strategy Pattern
--------------------
Intent:
Define a family of algorithms, encapsulate each one, and make them interchangeable.
Use Case:
Payment options, compression algorithms
Code Example:
interface IStrategy {
void Execute();
class StrategyA : IStrategy {
public void Execute() => Console.WriteLine("Executing Strategy A");
class StrategyB : IStrategy {
public void Execute() => Console.WriteLine("Executing Strategy B");
}
class Context {
private IStrategy _strategy;
public Context(IStrategy strategy) => _strategy = strategy;
public void SetStrategy(IStrategy strategy) => _strategy = strategy;
public void ExecuteStrategy() => _strategy.Execute();
Pros:
- Easy to switch algorithms
- Eliminates conditionals
Cons:
- More code structure
- Overhead of additional classes
------------------------------------------------------------
Design Pattern Comparison Table
-------------------------------
| Pattern | Type | Intent | Use Case |
|------------------|-------------|---------------------------------------|-------------------------|
| Singleton | Creational | Ensure a single instance | Logger, Config Manager |
| Factory Method | Creational | Delegate instantiation to subclass | Button, Shape Factory |
| Abstract Factory | Creational | Create related object families | Cross-platform UI |
| Adapter | Structural | Match incompatible interfaces | Legacy integration |
| Observer | Behavioral | Notify multiple objects of change | Event systems |
| Strategy | Behavioral | Select algorithm at runtime | Payment systems |