Design Patterns in C# - Quick Interview Reference
Creational Patterns
1. Singleton
When: Need exactly one instance of a class.
public sealed class Logger { private static readonly Lazy<Logger> _instance = new Lazy<Logger>(() => new Logger()); public static Logger Instance => _instance.Value; private Logger() { } // Private constructor public void Log(string message) => Console.WriteLine(message); } // Usage Logger.Instance.Log("Hello Singleton!");
2. Factory Method
When: Let subclasses decide which class to instantiate.
public abstract class Document { public abstract void CreatePages(); } public class Resume : Document { public override void CreatePages() => Console.WriteLine("Creating resume pages"); } public class Report : Document { public override void CreatePages() => Console.WriteLine("Creating report pages"); } // Factory public class DocumentFactory { public Document CreateDocument(string type) => type.ToLower() switch { "resume" => new Resume(), "report" => new Report(), _ => throw new ArgumentException("Invalid type") }; }
3. Builder
When: Complex object construction with many parts.
public class Pizza { public string Dough { get; set; } public string Sauce { get; set; } public List<string> Toppings { get; set; } = new(); } public class PizzaBuilder { private Pizza _pizza = new Pizza(); public PizzaBuilder SetDough(string dough) { _pizza.Dough = dough; return this; } public PizzaBuilder SetSauce(string sauce) { _pizza.Sauce = sauce; return this; } public PizzaBuilder AddTopping(string topping) { _pizza.Toppings.Add(topping); return this; } public Pizza Build() => _pizza; } // Usage var pizza = new PizzaBuilder() .SetDough("Thin") .SetSauce("Tomato") .AddTopping("Cheese") .AddTopping("Pepperoni") .Build();
Structural Patterns
4. Adapter
When: Make incompatible interfaces work together.
// Existing class public class LegacyPrinter { public void PrintDocument(string text) => Console.WriteLine($"Legacy: {text}"); } // New interface public interface IModernPrinter { void Print(string content); } // Adapter public class PrinterAdapter : IModernPrinter { private LegacyPrinter _legacyPrinter = new LegacyPrinter(); public void Print(string content) => _legacyPrinter.PrintDocument(content); }
5. Decorator
When: Add functionality to objects dynamically.
public interface ICoffee { string GetDescription(); double GetCost(); } public class SimpleCoffee : ICoffee { public string GetDescription() => "Simple coffee"; public double GetCost() => 5.0; } public class MilkDecorator : ICoffee { private ICoffee _coffee; public MilkDecorator(ICoffee coffee) => _coffee = coffee; public string GetDescription() => _coffee.GetDescription() + ", Milk"; public double GetCost() => _coffee.GetCost() + 1.5; } // Usage ICoffee coffee = new SimpleCoffee(); coffee = new MilkDecorator(coffee); Console.WriteLine($"{coffee.GetDescription()} costs ${coffee.GetCost()}");
Behavioral Patterns
6. Observer
When: One-to-many dependency between objects.
public interface IObserver { void Update(string message); } public class Subject { private List<IObserver> _observers = new(); public void Subscribe(IObserver observer) => _observers.Add(observer); public void Unsubscribe(IObserver observer) => _observers.Remove(observer); public void Notify(string message) => _observers.ForEach(o => o.Update(message)); } public class User : IObserver { public void Update(string message) => Console.WriteLine($"User received: {message}"); }
7. Strategy
When: Multiple algorithms for a task, choose at runtime.
public interface IPaymentStrategy { void Pay(double amount); } public class CreditCardPayment : IPaymentStrategy { public void Pay(double amount) => Console.WriteLine($"Paid ${amount} via Credit Card"); } public class PayPalPayment : IPaymentStrategy { public void Pay(double amount) => Console.WriteLine($"Paid ${amount} via PayPal"); } public class PaymentContext { private IPaymentStrategy _strategy; public void SetStrategy(IPaymentStrategy strategy) => _strategy = strategy; public void ExecutePayment(double amount) => _strategy?.Pay(amount); }
8. Repository Pattern (Common in interviews)
When: Abstract data access layer.
public interface IRepository<T> { T GetById(int id); void Add(T entity); void Update(T entity); void Delete(int id); } public class UserRepository : IRepository<User> { private List<User> _users = new(); public User GetById(int id) => _users.FirstOrDefault(u => u.Id == id); public void Add(User user) => _users.Add(user); public void Update(User user) { /* Update logic */ } public void Delete(int id) => _users.RemoveAll(u => u.Id == id); }
Quick Interview Cheat Sheet:
| Pattern | When to Use | Key Point |
|---|---|---|
| Singleton | One instance needed | Private constructor, static instance |
| Factory | Object creation logic | Centralized creation |
| Builder | Complex object construction | Fluent interface, step-by-step |
| Adapter | Interface compatibility | Wrapper class |
| Decorator | Dynamic functionality | Wrapper with same interface |
| Observer | Event notifications | Publisher-Subscriber |
| Strategy | Multiple algorithms | Interchangeable behaviors |
| Repository | Data access abstraction | CRUD operations |
Common Interview Questions:
"When would you use Strategy vs Factory?"
Strategy: Different behaviors/algorithms
Factory: Different object creation
"What's the difference between Adapter and Decorator?"
Adapter: Changes interface
Decorator: Adds functionality
"Why avoid Singleton?"
Hard to test, global state, tight coupling
These patterns show you understand scalable, maintainable code design! 🚀
Comments
Post a Comment