Chapter 3: Design Patterns - Mastering Object Creation - LLD Series - IndianTechnoEra
Latest update Android YouTube

Chapter 3: Design Patterns - Mastering Object Creation - LLD Series

Creational Design Patterns for LLD

Series: Low Level Design for .NET Developers | Previous: Chapter 2: SOLID Principles | Next: Chapter 4: Structural Design Patterns


📖 Introduction

Creational design patterns deal with object creation mechanisms. While creating objects in C# seems simple with the new keyword, complex applications require more sophisticated approaches. These patterns give you control over:

  • What gets created
  • Who creates it
  • When it gets created
  • How it gets created

In this chapter, we'll explore five essential creational patterns:

Pattern Purpose When to Use
Singleton Ensure only one instance exists Configuration, logging, caching, connection pools
Factory Method Define interface for creation, let subclasses decide When class can't anticipate object types it needs
Abstract Factory Create families of related objects UI toolkits, cross-platform applications
Builder Construct complex objects step by step Objects with many optional parameters, complex construction
Prototype Create objects by cloning existing ones When object creation is expensive, need to avoid initialization cost

1. Singleton Pattern

1.1 Understanding Singleton

Definition: The Singleton pattern ensures a class has only one instance and provides a global point of access to it.

Real-World Analogy: A country has only one government. Any citizen accesses the same government instance. If a new government was created every time someone needed it, there would be chaos!

Common Use Cases:

  • Configuration managers
  • Logging services
  • Database connection pools
  • Cache managers
  • Thread pools

1.2 ❌ Non-Thread-Safe Singleton

// ❌ WARNING: Not thread-safe!
public sealed class ConfigurationManager
{
    private static ConfigurationManager _instance;
    
    private ConfigurationManager()
    {
        // Private constructor prevents external instantiation
        Console.WriteLine("ConfigurationManager created");
        LoadConfiguration();
    }
    
    public static ConfigurationManager Instance
    {
        get
        {
            if (_instance == null)  // Multiple threads can enter here simultaneously
            {
                _instance = new ConfigurationManager();
            }
            return _instance;
        }
    }
    
    public string DatabaseConnectionString { get; private set; }
    
    private void LoadConfiguration()
    {
        // Simulate loading configuration
        DatabaseConnectionString = "Server=.;Database=MyApp;Trusted_Connection=true";
        Thread.Sleep(100); // Simulate work
    }
}
// Problem: In multi-threaded environment, two threads can create two instances!

1.3 ✅ Thread-Safe Singleton with Lazy<T>

// ✅ Best practice: Thread-safe, lazy initialization
public sealed class ConfigurationManager
{
    // Lazy<T> ensures thread safety and lazy initialization
    private static readonly Lazy<ConfigurationManager> _instance = 
        new Lazy<ConfigurationManager>(() => new ConfigurationManager());
    
    private ConfigurationManager()
    {
        Console.WriteLine("ConfigurationManager created (thread-safe)");
        LoadConfiguration();
    }
    
    public static ConfigurationManager Instance => _instance.Value;
    
    public string DatabaseConnectionString { get; private set; }
    public string ApiKey { get; private set; }
    public string Environment { get; private set; }
    public int MaxRetryCount { get; private set; }
    
    private void LoadConfiguration()
    {
        // Simulate loading from appsettings.json or environment
        DatabaseConnectionString = "Server=.;Database=MyApp;Trusted_Connection=true";
        ApiKey = "your-api-key-here";
        Environment = "Production";
        MaxRetryCount = 3;
        
        Console.WriteLine("Configuration loaded successfully");
    }
    
    public void ReloadConfiguration()
    {
        Console.WriteLine("Reloading configuration...");
        LoadConfiguration();
    }
}

// Usage
public class ApplicationStartup
{
    public void Initialize()
    {
        // Single instance accessed globally
        var config1 = ConfigurationManager.Instance;
        var config2 = ConfigurationManager.Instance;
        
        Console.WriteLine($"Same instance? {ReferenceEquals(config1, config2)}"); // True
        
        Console.WriteLine($"DB Connection: {config1.DatabaseConnectionString}");
        Console.WriteLine($"API Key: {config1.ApiKey}");
    }
}

1.4 Singleton with Double-Check Locking (Alternative)

// Alternative approach: Double-check locking (for .NET Framework compatibility)
public sealed class Logger
{
    private static Logger _instance;
    private static readonly object _lock = new object();
    
    private readonly List<string> _logs;
    private readonly string _logFilePath;
    
    private Logger()
    {
        _logs = new List<string>();
        _logFilePath = $"logs\\app_{DateTime.Now:yyyyMMdd}.log";
        Directory.CreateDirectory("logs");
        Console.WriteLine("Logger initialized");
    }
    
    public static Logger Instance
    {
        get
        {
            if (_instance == null)  // First check (no locking)
            {
                lock (_lock)  // Lock only if instance doesn't exist
                {
                    if (_instance == null)  // Second check
                    {
                        _instance = new Logger();
                    }
                }
            }
            return _instance;
        }
    }
    
    public void LogInfo(string message)
    {
        var logEntry = $"[INFO] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}";
        _logs.Add(logEntry);
        File.AppendAllText(_logFilePath, logEntry + Environment.NewLine);
        Console.WriteLine(logEntry);
    }
    
    public void LogError(string message, Exception ex = null)
    {
        var logEntry = $"[ERROR] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}";
        if (ex != null)
            logEntry += $"\nException: {ex.Message}\nStack Trace: {ex.StackTrace}";
        
        _logs.Add(logEntry);
        File.AppendAllText(_logFilePath, logEntry + Environment.NewLine);
        Console.WriteLine(logEntry);
    }
    
    public List<string> GetRecentLogs(int count = 10)
    {
        return _logs.TakeLast(count).ToList();
    }
}

// Usage across multiple classes - all use the same logger instance
public class OrderService
{
    public void ProcessOrder(int orderId)
    {
        Logger.Instance.LogInfo($"Processing order {orderId}");
        // Business logic...
        Logger.Instance.LogInfo($"Order {orderId} processed successfully");
    }
}

public class PaymentService
{
    public void ProcessPayment(decimal amount)
    {
        Logger.Instance.LogInfo($"Processing payment of {amount:C}");
        // Payment logic...
        Logger.Instance.LogInfo($"Payment of {amount:C} completed");
    }
}

💡 Singleton Best Practices:

  • Use Lazy<T> for simplest thread-safe implementation
  • Make the class sealed to prevent inheritance
  • Make constructor private to prevent external instantiation
  • Consider if you really need Singleton - sometimes static class is sufficient
  • Singleton makes unit testing harder - consider dependency injection alternatives

2. Factory Method Pattern

2.1 Understanding Factory Method

Definition: The Factory Method pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. It lets a class defer instantiation to subclasses.

Real-World Analogy: A recruitment agency (creator) doesn't know which specific candidate (product) will be hired. Different departments (subclasses) create different types of candidates.

2.2 ❌ Without Factory Method - Tight Coupling

public class DocumentProcessor
{
    private readonly string _documentType;
    
    public DocumentProcessor(string documentType)
    {
        _documentType = documentType;
    }
    
    public void Process(string filePath)
    {
        // Tight coupling - directly creating specific types
        if (_documentType == "PDF")
        {
            var parser = new PdfParser();
            parser.Parse(filePath);
            var validator = new PdfValidator();
            validator.Validate(parser.GetData());
        }
        else if (_documentType == "Excel")
        {
            var parser = new ExcelParser();
            parser.Parse(filePath);
            var validator = new ExcelValidator();
            validator.Validate(parser.GetData());
        }
        else if (_documentType == "Word")
        {
            var parser = new WordParser();
            parser.Parse(filePath);
            var validator = new WordValidator();
            validator.Validate(parser.GetData());
        }
        // Adding new document type requires modifying this class!
    }
}

2.3 ✅ Factory Method Implementation

// Product interface
public interface IDocumentParser
{
    DocumentData Parse(string filePath);
    string DocumentType { get; }
}

// Concrete products
public class PdfParser : IDocumentParser
{
    public string DocumentType => "PDF";
    
    public DocumentData Parse(string filePath)
    {
        Console.WriteLine($"Parsing PDF file: {filePath}");
        // PDF-specific parsing logic using iTextSharp or PdfPig
        return new DocumentData
        {
            Content = File.ReadAllBytes(filePath),
            Metadata = new Dictionary<string, string>
            {
                ["Pages"] = "10",
                ["Author"] = "John Doe"
            }
        };
    }
}

public class ExcelParser : IDocumentParser
{
    public string DocumentType => "Excel";
    
    public DocumentData Parse(string filePath)
    {
        Console.WriteLine($"Parsing Excel file: {filePath}");
        // Excel-specific parsing using EPPlus or ClosedXML
        return new DocumentData
        {
            Content = File.ReadAllBytes(filePath),
            Metadata = new Dictionary<string, string>
            {
                ["Worksheets"] = "3",
                ["Rows"] = "1000"
            }
        };
    }
}

public class WordParser : IDocumentParser
{
    public string DocumentType => "Word";
    
    public DocumentData Parse(string filePath)
    {
        Console.WriteLine($"Parsing Word file: {filePath}");
        // Word-specific parsing using OpenXml or DocX
        return new DocumentData
        {
            Content = File.ReadAllBytes(filePath),
            Metadata = new Dictionary<string, string>
            {
                ["Pages"] = "25",
                ["Words"] = "5000"
            }
        };
    }
}

// Document data transfer object
public class DocumentData
{
    public byte[] Content { get; set; }
    public Dictionary<string, string> Metadata { get; set; }
}

// Creator abstract class
public abstract class DocumentProcessor
{
    // Factory Method - subclasses override this
    public abstract IDocumentParser CreateParser();
    
    // Template method - uses the factory method
    public void ProcessDocument(string filePath)
    {
        Console.WriteLine($"Starting document processing for: {filePath}");
        
        var parser = CreateParser();
        Console.WriteLine($"Using {parser.DocumentType} parser");
        
        var data = parser.Parse(filePath);
        
        ValidateDocument(data);
        ExtractMetadata(data);
        GeneratePreview(data);
        
        Console.WriteLine($"Document processing completed\n");
    }
    
    protected virtual void ValidateDocument(DocumentData data)
    {
        Console.WriteLine("Validating document structure...");
        // Common validation logic
    }
    
    protected virtual void ExtractMetadata(DocumentData data)
    {
        Console.WriteLine("Extracting metadata...");
        foreach (var meta in data.Metadata)
        {
            Console.WriteLine($"  {meta.Key}: {meta.Value}");
        }
    }
    
    protected virtual void GeneratePreview(DocumentData data)
    {
        Console.WriteLine("Generating preview...");
        // Common preview generation logic
    }
}

// Concrete creators
public class PdfDocumentProcessor : DocumentProcessor
{
    public override IDocumentParser CreateParser()
    {
        return new PdfParser();
    }
    
    protected override void ValidateDocument(DocumentData data)
    {
        base.ValidateDocument(data);
        Console.WriteLine("PDF-specific validation: Checking digital signatures...");
    }
}

public class ExcelDocumentProcessor : DocumentProcessor
{
    public override IDocumentParser CreateParser()
    {
        return new ExcelParser();
    }
    
    protected override void ValidateDocument(DocumentData data)
    {
        base.ValidateDocument(data);
        Console.WriteLine("Excel-specific validation: Checking formulas...");
    }
}

public class WordDocumentProcessor : DocumentProcessor
{
    public override IDocumentParser CreateParser()
    {
        return new WordParser();
    }
}

// Usage
public class DocumentManagementSystem
{
    public void ProcessDocuments()
    {
        // Each processor handles its specific document type
        var processors = new List<DocumentProcessor>
        {
            new PdfDocumentProcessor(),
            new ExcelDocumentProcessor(),
            new WordDocumentProcessor()
        };
        
        foreach (var processor in processors)
        {
            processor.ProcessDocument($"sample.{processor.CreateParser().DocumentType.ToLower()}");
        }
    }
}

2.4 Factory Method with Dependency Injection

// Alternative: Factory Method with DI and registration
public interface IDocumentParserFactory
{
    IDocumentParser CreateParser(string documentType);
}

public class DocumentParserFactory : IDocumentParserFactory
{
    private readonly IServiceProvider _serviceProvider;
    
    public DocumentParserFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    
    public IDocumentParser CreateParser(string documentType)
    {
        return documentType.ToLower() switch
        {
            "pdf" => _serviceProvider.GetRequiredService<PdfParser>(),
            "excel" => _serviceProvider.GetRequiredService<ExcelParser>(),
            "word" => _serviceProvider.GetRequiredService<WordParser>(),
            _ => throw new NotSupportedException($"Document type {documentType} not supported")
        };
    }
}

// Document processor using the factory
public class FlexibleDocumentProcessor
{
    private readonly IDocumentParserFactory _parserFactory;
    
    public FlexibleDocumentProcessor(IDocumentParserFactory parserFactory)
    {
        _parserFactory = parserFactory;
    }
    
    public void ProcessDocument(string filePath, string documentType)
    {
        var parser = _parserFactory.CreateParser(documentType);
        var data = parser.Parse(filePath);
        
        Console.WriteLine($"Successfully processed {documentType} document");
        // Process the data...
    }
}

// DI Registration
// services.AddScoped<PdfParser>();
// services.AddScoped<ExcelParser>();
// services.AddScoped<WordParser>();
// services.AddScoped<IDocumentParserFactory, DocumentParserFactory>();
// services.AddScoped<FlexibleDocumentProcessor>();

3. Abstract Factory Pattern

3.1 Understanding Abstract Factory

Definition: The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

Real-World Analogy: A furniture store (abstract factory) sells complete sets of furniture (family of products). You can buy a Modern set (concrete factory) or a Victorian set, but each set includes a chair, a table, and a sofa that match each other.

3.2 Abstract Factory Implementation - UI Theme System

// Abstract Products
public interface IButton
{
    void Render();
    void OnClick();
}

public interface ITextBox
{
    void Render();
    string GetText();
    void SetText(string text);
}

public interface IWindow
{
    void Render();
    void Close();
    void Minimize();
}

// Concrete Products - Light Theme
public class LightButton : IButton
{
    public void Render()
    {
        Console.WriteLine("Rendering light button: white background, black text");
    }
    
    public void OnClick()
    {
        Console.WriteLine("Light button clicked with subtle animation");
    }
}

public class LightTextBox : ITextBox
{
    private string _text;
    
    public void Render()
    {
        Console.WriteLine("Rendering light textbox: white background, gray border");
    }
    
    public string GetText() => _text;
    
    public void SetText(string text)
    {
        _text = text;
        Console.WriteLine($"Light textbox text set to: {text}");
    }
}

public class LightWindow : IWindow
{
    public void Render()
    {
        Console.WriteLine("Rendering light window: white background, light gray border");
    }
    
    public void Close()
    {
        Console.WriteLine("Closing light window with fade effect");
    }
    
    public void Minimize()
    {
        Console.WriteLine("Minimizing light window with smooth animation");
    }
}

// Concrete Products - Dark Theme
public class DarkButton : IButton
{
    public void Render()
    {
        Console.WriteLine("Rendering dark button: dark gray background, white text");
    }
    
    public void OnClick()
    {
        Console.WriteLine("Dark button clicked with glow effect");
    }
}

public class DarkTextBox : ITextBox
{
    private string _text;
    
    public void Render()
    {
        Console.WriteLine("Rendering dark textbox: dark gray background, light gray border");
    }
    
    public string GetText() => _text;
    
    public void SetText(string text)
    {
        _text = text;
        Console.WriteLine($"Dark textbox text set to: {text}");
    }
}

public class DarkWindow : IWindow
{
    public void Render()
    {
        Console.WriteLine("Rendering dark window: dark background, subtle border");
    }
    
    public void Close()
    {
        Console.WriteLine("Closing dark window with fade to black effect");
    }
    
    public void Minimize()
    {
        Console.WriteLine("Minimizing dark window with smooth animation");
    }
}

// Abstract Factory
public interface IUIThemeFactory
{
    IButton CreateButton();
    ITextBox CreateTextBox();
    IWindow CreateWindow();
}

// Concrete Factories
public class LightThemeFactory : IUIThemeFactory
{
    public IButton CreateButton() => new LightButton();
    public ITextBox CreateTextBox() => new LightTextBox();
    public IWindow CreateWindow() => new LightWindow();
}

public class DarkThemeFactory : IUIThemeFactory
{
    public IButton CreateButton() => new DarkButton();
    public ITextBox CreateTextBox() => new DarkTextBox();
    public IWindow CreateWindow() => new DarkWindow();
}

// Client Application
public class Application
{
    private IUIThemeFactory _themeFactory;
    private IButton _button;
    private ITextBox _textBox;
    private IWindow _window;
    
    public Application(IUIThemeFactory themeFactory)
    {
        _themeFactory = themeFactory;
    }
    
    public void InitializeUI()
    {
        _button = _themeFactory.CreateButton();
        _textBox = _themeFactory.CreateTextBox();
        _window = _themeFactory.CreateWindow();
    }
    
    public void RenderUI()
    {
        Console.WriteLine("\n=== Rendering UI ===");
        _window.Render();
        _textBox.Render();
        _button.Render();
    }
    
    public void DemonstrateInteraction()
    {
        Console.WriteLine("\n=== User Interaction ===");
        _textBox.SetText("Hello, World!");
        Console.WriteLine($"Text in textbox: {_textBox.GetText()}");
        _button.OnClick();
        _window.Minimize();
    }
}

// Usage
public class ThemeDemo
{
    public static void Run()
    {
        // Use Light Theme
        Console.WriteLine("=== LIGHT THEME ===");
        IUIThemeFactory lightFactory = new LightThemeFactory();
        var app = new Application(lightFactory);
        app.InitializeUI();
        app.RenderUI();
        app.DemonstrateInteraction();
        
        // Switch to Dark Theme - just change the factory!
        Console.WriteLine("\n\n=== DARK THEME ===");
        IUIThemeFactory darkFactory = new DarkThemeFactory();
        app = new Application(darkFactory);
        app.InitializeUI();
        app.RenderUI();
        app.DemonstrateInteraction();
    }
}

3.3 Abstract Factory with Configuration

// Theme configuration
public enum Theme
{
    Light,
    Dark,
    Blue,      // New theme - just add new factory!
    HighContrast
}

public class ThemeFactoryProvider
{
    private readonly Dictionary<Theme, IUIThemeFactory> _factories;
    
    public ThemeFactoryProvider()
    {
        _factories = new Dictionary<Theme, IUIThemeFactory>
        {
            [Theme.Light] = new LightThemeFactory(),
            [Theme.Dark] = new DarkThemeFactory(),
            // [Theme.Blue] = new BlueThemeFactory(), // Add new theme without changing client code
        };
    }
    
    public IUIThemeFactory GetFactory(Theme theme)
    {
        if (_factories.TryGetValue(theme, out var factory))
            return factory;
            
        throw new NotSupportedException($"Theme {theme} not supported");
    }
}

// Dynamic theme switching
public class ThemeAwareApplication
{
    private IUIThemeFactory _currentTheme;
    private readonly ThemeFactoryProvider _factoryProvider;
    
    public ThemeAwareApplication(ThemeFactoryProvider factoryProvider)
    {
        _factoryProvider = factoryProvider;
        _currentTheme = _factoryProvider.GetFactory(Theme.Light); // Default
    }
    
    public void SwitchTheme(Theme theme)
    {
        Console.WriteLine($"\n=== Switching to {theme} Theme ===");
        _currentTheme = _factoryProvider.GetFactory(theme);
        ReinitializeUI();
    }
    
    private void ReinitializeUI()
    {
        // Recreate UI components with new theme
        var button = _currentTheme.CreateButton();
        var textBox = _currentTheme.CreateTextBox();
        var window = _currentTheme.CreateWindow();
        
        button.Render();
        textBox.Render();
        window.Render();
    }
}

4. Builder Pattern

4.1 Understanding Builder

Definition: The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations.

Real-World Analogy: Building a computer. You have the same construction process (assemble CPU, add RAM, install storage, etc.), but you can create different configurations (gaming PC, office PC, server).

4.2 ❌ Without Builder - Telescoping Constructor Anti-Pattern

public class Email
{
    public string To { get; set; }
    public string From { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }
    public List<string> Cc { get; set; }
    public List<string> Bcc { get; set; }
    public List<string> Attachments { get; set; }
    public bool IsHtml { get; set; }
    public Priority Priority { get; set; }
    public DateTime? SendOn { get; set; }
    public bool IsReadReceiptRequested { get; set; }
    
    // Telescoping constructor - many combinations!
    public Email(string to, string from) { }
    public Email(string to, string from, string subject) { }
    public Email(string to, string from, string subject, string body) { }
    // Need many more constructors for all combinations! This is unmaintainable.
}

4.3 ✅ Builder Pattern Implementation

// Product class
public class Email
{
    // Required properties
    public string To { get; private set; }
    public string From { get; private set; }
    
    // Optional properties with defaults
    public string Subject { get; private set; } = "";
    public string Body { get; private set; } = "";
    public List<string> Cc { get; private set; } = new List<string>();
    public List<string> Bcc { get; private set; } = new List<string>();
    public List<string> Attachments { get; private set; } = new List<string>();
    public bool IsHtml { get; private set; } = false;
    public Priority Priority { get; private set; } = Priority.Normal;
    public DateTime? SendOn { get; private set; } = null;
    public bool IsReadReceiptRequested { get; private set; } = false;
    
    // Private constructor - only Builder can create
    private Email() { }
    
    // Nested Builder class
    public class Builder
    {
        private readonly Email _email;
        
        public Builder(string to, string from)
        {
            _email = new Email();
            _email.To = to ?? throw new ArgumentNullException(nameof(to));
            _email.From = from ?? throw new ArgumentNullException(nameof(from));
        }
        
        public Builder WithSubject(string subject)
        {
            _email.Subject = subject ?? "";
            return this;
        }
        
        public Builder WithBody(string body, bool isHtml = false)
        {
            _email.Body = body ?? "";
            _email.IsHtml = isHtml;
            return this;
        }
        
        public Builder AddCc(params string[] emails)
        {
            _email.Cc.AddRange(emails);
            return this;
        }
        
        public Builder AddBcc(params string[] emails)
        {
            _email.Bcc.AddRange(emails);
            return this;
        }
        
        public Builder AddAttachment(string filePath)
        {
            if (!File.Exists(filePath))
                throw new FileNotFoundException($"Attachment not found: {filePath}");
                
            _email.Attachments.Add(filePath);
            return this;
        }
        
        public Builder WithPriority(Priority priority)
        {
            _email.Priority = priority;
            return this;
        }
        
        public Builder ScheduleSend(DateTime sendOn)
        {
            if (sendOn < DateTime.Now)
                throw new ArgumentException("Send date must be in the future");
                
            _email.SendOn = sendOn;
            return this;
        }
        
        public Builder RequestReadReceipt()
        {
            _email.IsReadReceiptRequested = true;
            return this;
        }
        
        public Email Build()
        {
            // Validation before building
            if (string.IsNullOrEmpty(_email.To))
                throw new InvalidOperationException("Email must have a recipient");
                
            if (string.IsNullOrEmpty(_email.From))
                throw new InvalidOperationException("Email must have a sender");
                
            if (string.IsNullOrEmpty(_email.Body) && string.IsNullOrEmpty(_email.Subject))
                throw new InvalidOperationException("Email must have either subject or body");
                
            return _email;
        }
    }
    
    public override string ToString()
    {
        var sb = new StringBuilder();
        sb.AppendLine($"To: {To}");
        sb.AppendLine($"From: {From}");
        if (Cc.Any()) sb.AppendLine($"Cc: {string.Join(", ", Cc)}");
        if (Bcc.Any()) sb.AppendLine($"Bcc: {string.Join(", ", Bcc)}");
        if (!string.IsNullOrEmpty(Subject)) sb.AppendLine($"Subject: {Subject}");
        sb.AppendLine($"Body: {(IsHtml ? "HTML" : "Text")}");
        if (Attachments.Any()) sb.AppendLine($"Attachments: {string.Join(", ", Attachments)}");
        sb.AppendLine($"Priority: {Priority}");
        if (SendOn.HasValue) sb.AppendLine($"Scheduled: {SendOn.Value}");
        if (IsReadReceiptRequested) sb.AppendLine("Read receipt requested");
        return sb.ToString();
    }
}

public enum Priority
{
    Low,
    Normal,
    High,
    Urgent
}

// Usage - Fluent interface
public class EmailService
{
    public void SendEmail()
    {
        // Simple email
        var simpleEmail = new Email.Builder("user@example.com", "noreply@company.com")
            .WithSubject("Welcome!")
            .WithBody("Thank you for signing up!", isHtml: false)
            .Build();
        
        Console.WriteLine("Simple Email:");
        Console.WriteLine(simpleEmail);
        
        // Complex email with all options
        var complexEmail = new Email.Builder("customer@example.com", "support@company.com")
            .WithSubject("Your Order #12345")
            .WithBody("<h1>Order Confirmation</h1><p>Your order has been shipped!</p>", isHtml: true)
            .AddCc("manager@company.com", "warehouse@company.com")
            .AddBcc("audit@company.com")
            .AddAttachment(@"C:\invoices\invoice_12345.pdf")
            .AddAttachment(@"C:\receipts\receipt_12345.pdf")
            .WithPriority(Priority.High)
            .ScheduleSend(DateTime.Now.AddHours(2))
            .RequestReadReceipt()
            .Build();
        
        Console.WriteLine("\nComplex Email:");
        Console.WriteLine(complexEmail);
    }
}

4.4 Director - Reusing Construction Processes

// Director class - defines reusable construction processes
public class EmailDirector
{
    public Email BuildWelcomeEmail(string email, string userName)
    {
        return new Email.Builder(email, "welcome@company.com")
            .WithSubject($"Welcome to Our Platform, {userName}!")
            .WithBody($@"
                

Welcome {userName}!

Thank you for joining our platform. We're excited to have you!

Get started by completing your profile and exploring our features.

Best regards,
The Team

", isHtml: true) .Build(); } public Email BuildPasswordResetEmail(string email, string resetToken) { return new Email.Builder(email, "security@company.com") .WithSubject("Password Reset Request") .WithBody($@"

Password Reset

We received a request to reset your password.

Click here to reset your password.

If you didn't request this, please ignore this email.

", isHtml: true) .WithPriority(Priority.High) .Build(); } public Email BuildOrderConfirmation(string email, int orderId, List<OrderItem> items) { var body = new StringBuilder(); body.AppendLine($"<h1>Order #{orderId} Confirmed!</h1>"); body.AppendLine("<table border='1'>"); body.AppendLine("<tr><th>Product</th><th>Quantity</th><th>Price</th></tr>"); foreach (var item in items) { body.AppendLine($"<tr><td>{item.ProductName}</td><td>{item.Quantity}</td><td>{item.Price:C}</td></tr>"); } body.AppendLine("</table>"); return new Email.Builder(email, "orders@company.com") .WithSubject($"Order #{orderId} Confirmation") .WithBody(body.ToString(), isHtml: true) .AddAttachment($"invoices/invoice_{orderId}.pdf") .Build(); } } // Usage with Director public class AutomatedEmailSystem { private readonly EmailDirector _director; public AutomatedEmailSystem() { _director = new EmailDirector(); } public async Task SendWelcomeEmailAsync(string email, string userName) { var welcomeEmail = _director.BuildWelcomeEmail(email, userName); await SendAsync(welcomeEmail); Console.WriteLine($"Welcome email sent to {email}"); } public async Task SendPasswordResetAsync(string email, string resetToken) { var resetEmail = _director.BuildPasswordResetEmail(email, resetToken); await SendAsync(resetEmail); Console.WriteLine($"Password reset email sent to {email}"); } private Task SendAsync(Email email) { // Actual email sending logic Console.WriteLine($"Sending email:\n{email}"); return Task.CompletedTask; } }

💡 Builder Pattern Benefits:

  • Fluent interface: Readable, expressive code
  • Immutability: Product can be immutable after construction
  • Validation: All validation happens in Build() method
  • Reusable construction processes: Director can define standard configurations
  • Avoid telescoping constructors: No more 10-parameter constructors

5. Prototype Pattern

5.1 Understanding Prototype

Definition: The Prototype pattern creates new objects by cloning existing instances rather than calling constructors. This is useful when object creation is expensive or complex.

Real-World Analogy: Cell division - cells replicate by cloning themselves rather than building from scratch each time.

5.2 Prototype Implementation with ICloneable

// Product class with cloning support
public class Product : ICloneable
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }
    public List<string> Tags { get; set; }
    public Dictionary<string, string> Attributes { get; set; }
    public DateTime CreatedAt { get; set; }
    
    public Product()
    {
        Tags = new List<string>();
        Attributes = new Dictionary<string, string>();
        CreatedAt = DateTime.Now;
    }
    
    // Shallow clone - copies value types and references
    public object Clone()
    {
        return this.MemberwiseClone();
    }
    
    // Deep clone - creates new instances of reference types
    public Product DeepClone()
    {
        var cloned = (Product)this.MemberwiseClone();
        cloned.Tags = new List<string>(this.Tags);
        cloned.Attributes = new Dictionary<string, string>(this.Attributes);
        return cloned;
    }
    
    public override string ToString()
    {
        return $"Product: {Name} (${Price}) - Tags: {string.Join(", ", Tags)}";
    }
}

// Document template with cloning
public class DocumentTemplate : ICloneable
{
    public string Name { get; set; }
    public string Content { get; set; }
    public Dictionary<string, string> Placeholders { get; set; }
    public List<string> Sections { get; set; }
    public DocumentStyle Style { get; set; }
    
    public DocumentTemplate()
    {
        Placeholders = new Dictionary<string, string>();
        Sections = new List<string>();
        Style = new DocumentStyle();
    }
    
    public object Clone()
    {
        var cloned = (DocumentTemplate)this.MemberwiseClone();
        cloned.Placeholders = new Dictionary<string, string>(this.Placeholders);
        cloned.Sections = new List<string>(this.Sections);
        cloned.Style = (DocumentStyle)this.Style.Clone();
        return cloned;
    }
}

public class DocumentStyle : ICloneable
{
    public string FontFamily { get; set; } = "Arial";
    public int FontSize { get; set; } = 12;
    public string Color { get; set; } = "Black";
    public bool IsBold { get; set; } = false;
    public bool IsItalic { get; set; } = false;
    
    public object Clone()
    {
        return this.MemberwiseClone();
    }
}

// Prototype Registry - stores and manages prototypes
public class DocumentTemplateRegistry
{
    private readonly Dictionary<string, DocumentTemplate> _templates = new();
    
    public void RegisterTemplate(string key, DocumentTemplate template)
    {
        _templates[key] = template;
    }
    
    public DocumentTemplate GetTemplate(string key)
    {
        if (_templates.TryGetValue(key, out var template))
        {
            return (DocumentTemplate)template.Clone(); // Return a clone
        }
        throw new KeyNotFoundException($"Template '{key}' not found");
    }
    
    public void InitializeTemplates()
    {
        // Invoice template
        var invoiceTemplate = new DocumentTemplate
        {
            Name = "Invoice",
            Sections = new List<string> { "Header", "Company Details", "Customer Details", "Items", "Total", "Footer" },
            Placeholders = new Dictionary<string, string>
            {
                ["{InvoiceNumber}"] = "",
                ["{Date}"] = "",
                ["{CustomerName}"] = "",
                ["{Total}"] = ""
            }
        };
        RegisterTemplate("invoice", invoiceTemplate);
        
        // Report template
        var reportTemplate = new DocumentTemplate
        {
            Name = "Report",
            Sections = new List<string> { "Title", "Executive Summary", "Data", "Analysis", "Conclusion" },
            Placeholders = new Dictionary<string, string>
            {
                ["{ReportTitle}"] = "",
                ["{Author}"] = "",
                ["{Date}"] = "",
                ["{Summary}"] = ""
            },
            Style = new DocumentStyle { FontFamily = "Times New Roman", FontSize = 11 }
        };
        RegisterTemplate("report", reportTemplate);
        
        // Letter template
        var letterTemplate = new DocumentTemplate
        {
            Name = "Letter",
            Sections = new List<string> { "Sender", "Date", "Recipient", "Subject", "Body", "Closing" },
            Placeholders = new Dictionary<string, string>
            {
                ["{SenderName}"] = "",
                ["{RecipientName}"] = "",
                ["{Subject}"] = "",
                ["{Body}"] = ""
            },
            Style = new DocumentStyle { FontFamily = "Georgia", FontSize = 12, IsItalic = false }
        };
        RegisterTemplate("letter", letterTemplate);
    }
}

// Usage
public class DocumentGenerationSystem
{
    private readonly DocumentTemplateRegistry _registry;
    
    public DocumentGenerationSystem()
    {
        _registry = new DocumentTemplateRegistry();
        _registry.InitializeTemplates();
    }
    
    public void GenerateInvoice(string customerName, decimal total)
    {
        var invoice = _registry.GetTemplate("invoice");
        
        // Customize the cloned template
        invoice.Placeholders["{InvoiceNumber}"] = $"INV-{DateTime.Now:yyyyMMdd}-{new Random().Next(1000, 9999)}";
        invoice.Placeholders["{Date}"] = DateTime.Now.ToShortDateString();
        invoice.Placeholders["{CustomerName}"] = customerName;
        invoice.Placeholders["{Total}"] = total.ToString("C");
        
        Console.WriteLine($"\n=== Generated {invoice.Name} ===");
        Console.WriteLine($"Sections: {string.Join(" -> ", invoice.Sections)}");
        Console.WriteLine($"Style: {invoice.Style.FontFamily}, {invoice.Style.FontSize}pt");
        foreach (var placeholder in invoice.Placeholders)
        {
            Console.WriteLine($"{placeholder.Key} = {placeholder.Value}");
        }
    }
    
    public void GenerateReport(string title, string author)
    {
        var report = _registry.GetTemplate("report");
        
        report.Placeholders["{ReportTitle}"] = title;
        report.Placeholders["{Author}"] = author;
        report.Placeholders["{Date}"] = DateTime.Now.ToShortDateString();
        report.Placeholders["{Summary}"] = "This is a generated summary...";
        
        Console.WriteLine($"\n=== Generated {report.Name} ===");
        Console.WriteLine($"Title: {report.Placeholders["{ReportTitle}"]}");
        Console.WriteLine($"Author: {author}");
        Console.WriteLine($"Style: {report.Style.FontFamily}, {report.Style.FontSize}pt");
    }
}

// Performance comparison - Constructor vs Prototype
public class PerformanceComparison
{
    public void Compare()
    {
        // Expensive object creation (simulated)
        Console.WriteLine("Creating complex object via constructor...");
        var sw = System.Diagnostics.Stopwatch.StartNew();
        
        for (int i = 0; i < 10000; i++)
        {
            var product = new Product
            {
                Id = i,
                Name = "Product " + i,
                Price = 99.99m,
                Category = "Electronics",
                Tags = new List<string> { "new", "sale", "popular" },
                Attributes = new Dictionary<string, string>
                {
                    ["color"] = "red",
                    ["size"] = "large",
                    ["material"] = "plastic"
                }
            };
        }
        
        sw.Stop();
        Console.WriteLine($"Constructor: {sw.ElapsedMilliseconds}ms");
        
        // Create a prototype
        var prototype = new Product
        {
            Id = 0,
            Name = "Template",
            Price = 99.99m,
            Category = "Electronics",
            Tags = new List<string> { "new", "sale", "popular" },
            Attributes = new Dictionary<string, string>
            {
                ["color"] = "red",
                ["size"] = "large",
                ["material"] = "plastic"
            }
        };
        
        Console.WriteLine("Creating complex object via cloning...");
        sw.Restart();
        
        for (int i = 0; i < 10000; i++)
        {
            var product = prototype.DeepClone();
            product.Id = i;
            product.Name = "Product " + i;
        }
        
        sw.Stop();
        Console.WriteLine($"Prototype: {sw.ElapsedMilliseconds}ms");
    }
}

6. Choosing the Right Creational Pattern

Scenario Recommended Pattern Reason
Need exactly one instance of a class Singleton Ensures single instance with global access
Creating objects based on input/configuration Factory Method Encapsulates creation logic, easy to extend
Creating families of related objects Abstract Factory Ensures compatibility between created objects
Objects with many optional parameters Builder Fluent interface, avoids telescoping constructors
Expensive object creation, many similar instances Prototype Clone instead of recreating from scratch
Need to hide creation complexity Factory Method or Abstract Factory Encapsulates complex creation logic
Runtime configuration of object creation Abstract Factory Can swap factories at runtime

7. Real-World Example: E-Commerce Order System

Combining multiple creational patterns in a real-world scenario:

// ===== Singleton: Order Number Generator =====
public sealed class OrderNumberGenerator
{
    private static readonly Lazy<OrderNumberGenerator> _instance = 
        new Lazy<OrderNumberGenerator>(() => new OrderNumberGenerator());
    
    private int _currentNumber;
    private readonly object _lock = new object();
    
    private OrderNumberGenerator()
    {
        _currentNumber = DateTime.Now.Year * 10000;
    }
    
    public static OrderNumberGenerator Instance => _instance.Value;
    
    public string GenerateOrderNumber()
    {
        lock (_lock)
        {
            _currentNumber++;
            return $"ORD-{_currentNumber}";
        }
    }
}

// ===== Abstract Factory: Payment Processing =====
public interface IPaymentProcessor
{
    Task<PaymentResult> ProcessPaymentAsync(decimal amount);
}

public interface IRefundProcessor
{
    Task<RefundResult> ProcessRefundAsync(string transactionId, decimal amount);
}

public interface IPaymentFactory
{
    IPaymentProcessor CreatePaymentProcessor();
    IRefundProcessor CreateRefundProcessor();
}

public class StripePaymentFactory : IPaymentFactory
{
    private readonly string _apiKey;
    
    public StripePaymentFactory(string apiKey)
    {
        _apiKey = apiKey;
    }
    
    public IPaymentProcessor CreatePaymentProcessor() => new StripePaymentProcessor(_apiKey);
    public IRefundProcessor CreateRefundProcessor() => new StripeRefundProcessor(_apiKey);
}

public class PayPalPaymentFactory : IPaymentFactory
{
    private readonly string _clientId;
    private readonly string _clientSecret;
    
    public PayPalPaymentFactory(string clientId, string clientSecret)
    {
        _clientId = clientId;
        _clientSecret = clientSecret;
    }
    
    public IPaymentProcessor CreatePaymentProcessor() => new PayPalPaymentProcessor(_clientId, _clientSecret);
    public IRefundProcessor CreateRefundProcessor() => new PayPalRefundProcessor(_clientId, _clientSecret);
}

// Concrete implementations (simplified)
public class StripePaymentProcessor : IPaymentProcessor
{
    public StripePaymentProcessor(string apiKey) { }
    public Task<PaymentResult> ProcessPaymentAsync(decimal amount) => Task.FromResult(PaymentResult.Success());
}

public class StripeRefundProcessor : IRefundProcessor
{
    public StripeRefundProcessor(string apiKey) { }
    public Task<RefundResult> ProcessRefundAsync(string transactionId, decimal amount) => Task.FromResult(RefundResult.Success());
}

public class PayPalPaymentProcessor : IPaymentProcessor
{
    public PayPalPaymentProcessor(string clientId, string clientSecret) { }
    public Task<PaymentResult> ProcessPaymentAsync(decimal amount) => Task.FromResult(PaymentResult.Success());
}

public class PayPalRefundProcessor : IRefundProcessor
{
    public PayPalRefundProcessor(string clientId, string clientSecret) { }
    public Task<RefundResult> ProcessRefundAsync(string transactionId, decimal amount) => Task.FromResult(RefundResult.Success());
}

// ===== Builder: Order Construction =====
public class Order
{
    public string OrderNumber { get; private set; }
    public DateTime OrderDate { get; private set; }
    public Customer Customer { get; private set; }
    public List<OrderItem> Items { get; private set; }
    public ShippingAddress ShippingAddress { get; private set; }
    public PaymentMethod PaymentMethod { get; private set; }
    public decimal Subtotal { get; private set; }
    public decimal Tax { get; private set; }
    public decimal ShippingCost { get; private set; }
    public decimal Discount { get; private set; }
    public decimal Total { get; private set; }
    public string Notes { get; private set; }
    public OrderStatus Status { get; private set; }
    
    private Order() { }
    
    public class Builder
    {
        private readonly Order _order;
        
        public Builder(Customer customer)
        {
            _order = new Order();
            _order.OrderNumber = OrderNumberGenerator.Instance.GenerateOrderNumber();
            _order.OrderDate = DateTime.Now;
            _order.Customer = customer;
            _order.Items = new List<OrderItem>();
            _order.Status = OrderStatus.Pending;
        }
        
        public Builder AddItem(Product product, int quantity)
        {
            var item = new OrderItem
            {
                ProductId = product.Id,
                ProductName = product.Name,
                Quantity = quantity,
                UnitPrice = product.Price
            };
            _order.Items.Add(item);
            return this;
        }
        
        public Builder AddShippingAddress(string street, string city, string state, string zipCode, string country)
        {
            _order.ShippingAddress = new ShippingAddress
            {
                Street = street,
                City = city,
                State = state,
                ZipCode = zipCode,
                Country = country
            };
            return this;
        }
        
        public Builder SetPaymentMethod(PaymentMethod method)
        {
            _order.PaymentMethod = method;
            return this;
        }
        
        public Builder ApplyDiscount(decimal discountPercentage)
        {
            _order.Discount = _order.Subtotal * (discountPercentage / 100);
            return this;
        }
        
        public Builder ApplyPromoCode(string promoCode, decimal discountAmount)
        {
            _order.Discount = discountAmount;
            _order.Notes = $"Promo code applied: {promoCode}";
            return this;
        }
        
        public Builder AddNotes(string notes)
        {
            _order.Notes = notes;
            return this;
        }
        
        public Order Build()
        {
            // Calculate totals
            _order.Subtotal = _order.Items.Sum(i => i.UnitPrice * i.Quantity);
            _order.Tax = _order.Subtotal * 0.1m; // 10% tax
            _order.ShippingCost = CalculateShippingCost(_order.Items.Sum(i => i.Quantity));
            _order.Total = _order.Subtotal + _order.Tax + _order.ShippingCost - _order.Discount;
            
            // Validate
            if (_order.Items.Count == 0)
                throw new InvalidOperationException("Order must have at least one item");
                
            if (_order.ShippingAddress == null)
                throw new InvalidOperationException("Shipping address is required");
                
            return _order;
        }
        
        private decimal CalculateShippingCost(int totalItems)
        {
            if (totalItems > 10) return 0; // Free shipping
            if (totalItems > 5) return 5.99m;
            return 9.99m;
        }
    }
}

// ===== Factory Method: Notification Service =====
public interface INotification
{
    Task SendAsync(string recipient, string message);
}

public class EmailNotification : INotification
{
    public async Task SendAsync(string recipient, string message)
    {
        Console.WriteLine($"Sending email to {recipient}: {message}");
        await Task.Delay(100);
    }
}

public class SmsNotification : INotification
{
    public async Task SendAsync(string recipient, string message)
    {
        Console.WriteLine($"Sending SMS to {recipient}: {message}");
        await Task.Delay(50);
    }
}

public abstract class NotificationService
{
    public abstract INotification CreateNotification();
    
    public async Task NotifyAsync(string recipient, string message)
    {
        var notification = CreateNotification();
        await notification.SendAsync(recipient, message);
        Console.WriteLine($"Notification sent via {notification.GetType().Name}");
    }
}

public class EmailNotificationService : NotificationService
{
    public override INotification CreateNotification() => new EmailNotification();
}

public class SmsNotificationService : NotificationService
{
    public override INotification CreateNotification() => new SmsNotification();
}

// ===== Complete Order Processing System =====
public class OrderProcessingSystem
{
    private readonly IPaymentFactory _paymentFactory;
    private readonly NotificationService _notificationService;
    
    public OrderProcessingSystem(IPaymentFactory paymentFactory, NotificationService notificationService)
    {
        _paymentFactory = paymentFactory;
        _notificationService = notificationService;
    }
    
    public async Task<Order> ProcessOrderAsync(Order order)
    {
        Console.WriteLine($"\n=== Processing Order {order.OrderNumber} ===");
        
        // Process payment
        var paymentProcessor = _paymentFactory.CreatePaymentProcessor();
        var paymentResult = await paymentProcessor.ProcessPaymentAsync(order.Total);
        
        if (!paymentResult.IsSuccess)
        {
            throw new Exception("Payment failed");
        }
        
        // Update order status
        // order.Status = OrderStatus.Paid;
        
        // Send notification
        await _notificationService.NotifyAsync(order.Customer.Email, 
            $"Your order {order.OrderNumber} has been confirmed. Total: {order.Total:C}");
        
        Console.WriteLine($"Order {order.OrderNumber} processed successfully!");
        return order;
    }
}

// Usage
public class ECommerceDemo
{
    public static async Task RunAsync()
    {
        // Build an order using Builder pattern
        var customer = new Customer { Id = 1, Name = "John Doe", Email = "john@example.com" };
        
        var order = new Order.Builder(customer)
            .AddItem(new Product { Id = 1, Name = "Laptop", Price = 999.99m }, 1)
            .AddItem(new Product { Id = 2, Name = "Mouse", Price = 29.99m }, 2)
            .AddShippingAddress("123 Main St", "Springfield", "IL", "62701", "USA")
            .SetPaymentMethod(PaymentMethod.CreditCard)
            .ApplyPromoCode("SAVE20", 20)
            .AddNotes("Please deliver before 5 PM")
            .Build();
        
        // Create payment factory based on configuration (Abstract Factory)
        IPaymentFactory paymentFactory;
        var paymentProvider = "Stripe"; // From configuration
        
        if (paymentProvider == "Stripe")
            paymentFactory = new StripePaymentFactory("sk_test_123");
        else
            paymentFactory = new PayPalPaymentFactory("client_id", "client_secret");
        
        // Create notification service (Factory Method)
        NotificationService notificationService = new EmailNotificationService();
        
        // Process order
        var orderSystem = new OrderProcessingSystem(paymentFactory, notificationService);
        await orderSystem.ProcessOrderAsync(order);
    }
}

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

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

public class OrderItem
{
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
}

public class ShippingAddress
{
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
    public string Country { get; set; }
}

public enum PaymentMethod
{
    CreditCard,
    PayPal,
    Crypto
}

public enum OrderStatus
{
    Pending,
    Paid,
    Shipped,
    Delivered,
    Cancelled
}

public class PaymentResult
{
    public bool IsSuccess { get; set; }
    public static PaymentResult Success() => new PaymentResult { IsSuccess = true };
    public static PaymentResult Failed(string error) => new PaymentResult { IsSuccess = false };
}

public class RefundResult
{
    public bool IsSuccess { get; set; }
    public static RefundResult Success() => new RefundResult { IsSuccess = true };
}

8. Summary and Key Takeaways

  • Singleton: One instance, global access. Use Lazy<T> for thread safety.
  • Factory Method: Let subclasses decide which objects to create. Great for frameworks and libraries.
  • Abstract Factory: Create families of related objects. Perfect for cross-platform applications.
  • Builder: Construct complex objects step by step. Fluent interface makes code readable.
  • Prototype: Clone objects instead of creating from scratch. Ideal for expensive initialization.

🎯 When to Use Which Pattern:

  • Singleton: Configuration, logging, caching, connection pools
  • Factory Method: Document parsers, database providers, logger factories
  • Abstract Factory: UI themes, cross-platform libraries, database families
  • Builder: Email construction, SQL queries, complex DTOs, HTTP requests
  • Prototype: Document templates, expensive database queries, cached objects

9. Practice Exercises

Exercise 1: Implement a Cache Manager (Singleton)

Create a thread-safe cache manager with expiration policies.

Click to see solution
public sealed class CacheManager
{
    private static readonly Lazy<CacheManager> _instance = new(() => new CacheManager());
    private readonly ConcurrentDictionary<string, (object Value, DateTime Expiry)> _cache;
    private readonly Timer _cleanupTimer;
    
    private CacheManager()
    {
        _cache = new ConcurrentDictionary<string, (object, DateTime)>();
        _cleanupTimer = new Timer(CleanupExpired, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
    }
    
    public static CacheManager Instance => _instance.Value;
    
    public void Set(string key, object value, TimeSpan? expiry = null)
    {
        var expiryTime = expiry.HasValue ? DateTime.Now.Add(expiry.Value) : DateTime.Now.AddHours(1);
        _cache[key] = (value, expiryTime);
    }
    
    public T Get<T>(string key)
    {
        if (_cache.TryGetValue(key, out var entry))
        {
            if (entry.Expiry > DateTime.Now)
                return (T)entry.Value;
            else
                _cache.TryRemove(key, out _);
        }
        return default;
    }
    
    private void CleanupExpired(object state)
    {
        foreach (var key in _cache.Keys)
        {
            if (_cache.TryGetValue(key, out var entry) && entry.Expiry <= DateTime.Now)
                _cache.TryRemove(key, out _);
        }
    }
}

Exercise 2: Create a Database Connection Factory

Implement a factory that creates different database connections based on configuration.

Click to see solution
public interface IDatabaseConnection
{
    void Connect();
    void Disconnect();
    void ExecuteQuery(string query);
}

public class SqlServerConnection : IDatabaseConnection { /* implementation */ }
public class MySqlConnection : IDatabaseConnection { /* implementation */ }
public class PostgreSqlConnection : IDatabaseConnection { /* implementation */ }

public interface IDatabaseFactory
{
    IDatabaseConnection CreateConnection();
}

public class SqlServerFactory : IDatabaseFactory
{
    private readonly string _connectionString;
    public SqlServerFactory(string connectionString) => _connectionString = connectionString;
    public IDatabaseConnection CreateConnection() => new SqlServerConnection(_connectionString);
}

public class MySqlFactory : IDatabaseFactory
{
    private readonly string _connectionString;
    public MySqlFactory(string connectionString) => _connectionString = connectionString;
    public IDatabaseConnection CreateConnection() => new MySqlConnection(_connectionString);
}

10. What's Next?

In Chapter 4, we'll explore Structural Design Patterns - Adapter, Bridge, Composite, Decorator, Facade, Flyweight, and Proxy. These patterns help you compose objects and classes into larger structures while keeping them flexible and efficient.

Chapter 4 Preview:

  • Adapter - Make incompatible interfaces work together
  • Decorator - Add responsibilities dynamically
  • Facade - Simplify complex subsystems
  • Proxy - Control access to objects
  • Composite - Treat individual objects and compositions uniformly

📝 Practice Assignments:

  1. Implement a thread-safe logging service using Singleton pattern
  2. Create a document converter system using Factory Method (PDF, Word, Excel converters)
  3. Build a cross-platform UI framework using Abstract Factory
  4. Create a SQL query builder using Builder pattern with fluent interface
  5. Implement a prototype registry for report templates
  6. Combine multiple patterns to build a complete e-commerce checkout system

Happy Coding! 🚀

Post a Comment

Feel free to ask your query...
Cookie Consent
We serve cookies on this site to analyze traffic, remember your preferences, and optimize your experience.
Oops!
It seems there is something wrong with your internet connection. Please connect to the internet and start browsing again.
AdBlock Detected!
We have detected that you are using adblocking plugin in your browser.
The revenue we earn by the advertisements is used to manage this website, we request you to whitelist our website in your adblocking plugin.
Site is Blocked
Sorry! This site is not available in your country.