What are design patterns? Name and explain 5 commonly used patterns.

18 minintermediatedesign-patternsarchitecturebest-practices

Quick Answer

Design patterns are reusable solutions to common software design problems. Common patterns include: Singleton (single instance), Factory (object creation), Observer (event notification), Strategy (algorithm selection), and Repository (data access abstraction). Patterns provide proven solutions, improve code quality, and facilitate communication among developers.

Detailed Answer

Design Patterns are reusable solutions to common software design problems. They represent best practices and provide a template for solving specific issues in software development.

2.9.1. Singleton Pattern

Ensures a class has only one instance and provides a global point of access to it.

public sealed class DatabaseConnection
{
    private static DatabaseConnection instance = null;
    private static readonly object lockObject = new object();
    
    private DatabaseConnection()
    {
        // Private constructor prevents instantiation
    }
    
    public static DatabaseConnection Instance
    {
        get
        {
            lock (lockObject)
            {
                if (instance == null)
                {
                    instance = new DatabaseConnection();
                }
                return instance;
            }
        }
    }
    
    public void Query(string sql)
    {
        Console.WriteLine($"Executing: {sql}");
    }
}

// Usage
var db1 = DatabaseConnection.Instance;
var db2 = DatabaseConnection.Instance;
// db1 and db2 refer to the same instance

2.9.2. Factory Pattern

Creates objects without specifying the exact class to create.

public interface IPayment
{
    void ProcessPayment(decimal amount);
}

public class CreditCardPayment : IPayment
{
    public void ProcessPayment(decimal amount)
    {
        Console.WriteLine($"Processing credit card payment: ${amount}");
    }
}

public class PayPalPayment : IPayment
{
    public void ProcessPayment(decimal amount)
    {
        Console.WriteLine($"Processing PayPal payment: ${amount}");
    }
}

public class CryptoPayment : IPayment
{
    public void ProcessPayment(decimal amount)
    {
        Console.WriteLine($"Processing crypto payment: ${amount}");
    }
}

public class PaymentFactory
{
    public static IPayment CreatePayment(string paymentType)
    {
        return paymentType.ToLower() switch
        {
            "creditcard" => new CreditCardPayment(),
            "paypal" => new PayPalPayment(),
            "crypto" => new CryptoPayment(),
            _ => throw new ArgumentException("Invalid payment type")
        };
    }
}

// Usage
IPayment payment = PaymentFactory.CreatePayment("creditcard");
payment.ProcessPayment(100.50m);

2.9.3. Observer Pattern

Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified.

public interface IObserver
{
    void Update(string message);
}

public interface ISubject
{
    void Attach(IObserver observer);
    void Detach(IObserver observer);
    void Notify(string message);
}

public class NewsAgency : ISubject
{
    private List<IObserver> observers = new List<IObserver>();
    
    public void Attach(IObserver observer)
    {
        observers.Add(observer);
    }
    
    public void Detach(IObserver observer)
    {
        observers.Remove(observer);
    }
    
    public void Notify(string message)
    {
        foreach (var observer in observers)
        {
            observer.Update(message);
        }
    }
    
    public void PublishNews(string news)
    {
        Console.WriteLine($"Breaking News: {news}");
        Notify(news);
    }
}

public class NewsChannel : IObserver
{
    public string Name { get; set; }
    
    public NewsChannel(string name)
    {
        Name = name;
    }
    
    public void Update(string message)
    {
        Console.WriteLine($"{Name} received news: {message}");
    }
}

// Usage
var agency = new NewsAgency();
var channel1 = new NewsChannel("CNN");
var channel2 = new NewsChannel("BBC");

agency.Attach(channel1);
agency.Attach(channel2);
agency.PublishNews("Major event occurred!");

2.9.4. Strategy Pattern

Defines a family of algorithms, encapsulates each one, and makes them interchangeable.

public interface ISortStrategy
{
    void Sort(List<int> list);
}

public class BubbleSort : ISortStrategy
{
    public void Sort(List<int> list)
    {
        Console.WriteLine("Sorting using Bubble Sort");
        // Bubble sort implementation
        for (int i = 0; i < list.Count - 1; i++)
        {
            for (int j = 0; j < list.Count - i - 1; j++)
            {
                if (list[j] > list[j + 1])
                {
                    int temp = list[j];
                    list[j] = list[j + 1];
                    list[j + 1] = temp;
                }
            }
        }
    }
}

public class QuickSort : ISortStrategy
{
    public void Sort(List<int> list)
    {
        Console.WriteLine("Sorting using Quick Sort");
        // Quick sort implementation
        list.Sort();
    }
}

public class SortContext
{
    private ISortStrategy sortStrategy;
    
    public void SetSortStrategy(ISortStrategy strategy)
    {
        sortStrategy = strategy;
    }
    
    public void SortList(List<int> list)
    {
        sortStrategy.Sort(list);
    }
}

// Usage
var numbers = new List<int> { 5, 2, 8, 1, 9 };
var context = new SortContext();

context.SetSortStrategy(new BubbleSort());
context.SortList(numbers);

context.SetSortStrategy(new QuickSort());
context.SortList(numbers);

2.9.5. Repository Pattern

Mediates between the domain and data mapping layers, acting like an in-memory collection of domain objects.

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public interface IRepository<T> where T : class
{
    T GetById(int id);
    IEnumerable<T> GetAll();
    void Add(T entity);
    void Update(T entity);
    void Delete(int id);
}

public class ProductRepository : IRepository<Product>
{
    private List<Product> products = new List<Product>();
    
    public Product GetById(int id)
    {
        return products.FirstOrDefault(p => p.Id == id);
    }
    
    public IEnumerable<Product> GetAll()
    {
        return products;
    }
    
    public void Add(Product entity)
    {
        products.Add(entity);
        Console.WriteLine($"Product {entity.Name} added");
    }
    
    public void Update(Product entity)
    {
        var existing = GetById(entity.Id);
        if (existing != null)
        {
            existing.Name = entity.Name;
            existing.Price = entity.Price;
            Console.WriteLine($"Product {entity.Id} updated");
        }
    }
    
    public void Delete(int id)
    {
        var product = GetById(id);
        if (product != null)
        {
            products.Remove(product);
            Console.WriteLine($"Product {id} deleted");
        }
    }
}

// Usage
var repository = new ProductRepository();
repository.Add(new Product { Id = 1, Name = "Laptop", Price = 999.99m });
repository.Add(new Product { Id = 2, Name = "Mouse", Price = 29.99m });

var allProducts = repository.GetAll();
var laptop = repository.GetById(1);

Related Resources