Design Patterns in C# - Quick Interview Reference

 

Creational Patterns

1. Singleton

When: Need exactly one instance of a class.

csharp
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.

csharp
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.

csharp
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.

csharp
// 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.

csharp
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.

csharp
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.

csharp
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.

csharp
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:

PatternWhen to UseKey Point
SingletonOne instance neededPrivate constructor, static instance
FactoryObject creation logicCentralized creation
BuilderComplex object constructionFluent interface, step-by-step
AdapterInterface compatibilityWrapper class
DecoratorDynamic functionalityWrapper with same interface
ObserverEvent notificationsPublisher-Subscriber
StrategyMultiple algorithmsInterchangeable behaviors
RepositoryData access abstractionCRUD operations

Common Interview Questions:

  1. "When would you use Strategy vs Factory?"

    • Strategy: Different behaviors/algorithms

    • Factory: Different object creation

  2. "What's the difference between Adapter and Decorator?"

    • Adapter: Changes interface

    • Decorator: Adds functionality

  3. "Why avoid Singleton?"

    • Hard to test, global state, tight coupling

These patterns show you understand scalable, maintainable code design! 🚀

Comments

Popular posts from this blog

.NET Core Interview Questions and Answers for 10+ Years Experienced Professionals

What are SOLID Principles?

.NET Core Senior Interview Q&A