Chapter 4: Structural Design Patterns - Composing Flexible Structures - LLD Series - IndianTechnoEra
Latest update Android YouTube

Chapter 4: Structural Design Patterns - Composing Flexible Structures - LLD Series

Structural Design Patterns for LLD

Series: Low Level Design for .NET Developers | Previous: Chapter 3: Creational Design Patterns | Next: Chapter 5: Behavioral Design Patterns


📖 Introduction

Structural design patterns deal with how classes and objects are composed to form larger structures. They help ensure that when parts of a system change, the entire structure doesn't need to change. These patterns focus on relationships between entities and help you achieve loose coupling while maintaining flexibility.

In this chapter, we'll explore seven essential structural patterns:

Pattern Purpose Real-World Analogy
Adapter Convert interface of one class to another Power plug adapter
Bridge Separate abstraction from implementation Remote control and TV
Composite Treat individual objects and compositions uniformly File system (files and folders)
Decorator Add responsibilities dynamically Pizza toppings
Facade Provide simplified interface to complex subsystem Restaurant menu (hides kitchen complexity)
Flyweight Share objects to reduce memory usage Character glyphs in word processor
Proxy Control access to another object Credit card (proxy for bank account)

1. Adapter Pattern

1.1 Understanding Adapter

Definition: The Adapter pattern allows objects with incompatible interfaces to collaborate. It acts as a bridge between two incompatible interfaces.

Real-World Analogy: When you travel to a different country, you use a power plug adapter to connect your device to foreign outlets. The adapter converts one interface to another that your device expects.

1.2 ❌ Without Adapter - Incompatible Systems

// Existing third-party payment gateway (cannot be modified)
public class StripePaymentGateway
{
    private string _apiKey;
    
    public StripePaymentGateway(string apiKey)
    {
        _apiKey = apiKey;
    }
    
    public void Charge(decimal amount, string currency, string cardToken)
    {
        Console.WriteLine($"Stripe: Charged {amount} {currency} using card {cardToken}");
    }
    
    public void Refund(string transactionId, decimal amount)
    {
        Console.WriteLine($"Stripe: Refunded {amount} for transaction {transactionId}");
    }
}

// Another third-party gateway
public class PayPalClient
{
    private string _clientId;
    private string _clientSecret;
    
    public PayPalClient(string clientId, string clientSecret)
    {
        _clientId = clientId;
        _clientSecret = clientSecret;
    }
    
    public void MakePayment(decimal amount, string currency, string email)
    {
        Console.WriteLine($"PayPal: Processed payment of {amount} {currency} for {email}");
    }
    
    public void IssueRefund(string paymentId, decimal amount)
    {
        Console.WriteLine($"PayPal: Refunded {amount} for payment {paymentId}");
    }
}

// Our application's expected interface
public interface IPaymentProcessor
{
    void ProcessPayment(decimal amount, string currency, string paymentDetails);
    void ProcessRefund(string transactionId, decimal amount);
}

// Problem: Stripe and PayPal have different interfaces. We need to adapt them.

1.3 ✅ Adapter Pattern Implementation

// Target interface - what our application expects
public interface IPaymentProcessor
{
    void ProcessPayment(decimal amount, string currency, string paymentDetails);
    void ProcessRefund(string transactionId, decimal amount);
}

// Adapter for Stripe
public class StripeAdapter : IPaymentProcessor
{
    private readonly StripePaymentGateway _stripeGateway;
    
    public StripeAdapter(string apiKey)
    {
        _stripeGateway = new StripePaymentGateway(apiKey);
    }
    
    public void ProcessPayment(decimal amount, string currency, string paymentDetails)
    {
        // paymentDetails is the card token for Stripe
        _stripeGateway.Charge(amount, currency, paymentDetails);
    }
    
    public void ProcessRefund(string transactionId, decimal amount)
    {
        _stripeGateway.Refund(transactionId, amount);
    }
}

// Adapter for PayPal
public class PayPalAdapter : IPaymentProcessor
{
    private readonly PayPalClient _payPalClient;
    
    public PayPalAdapter(string clientId, string clientSecret)
    {
        _payPalClient = new PayPalClient(clientId, clientSecret);
    }
    
    public void ProcessPayment(decimal amount, string currency, string paymentDetails)
    {
        // paymentDetails is the PayPal email for PayPal
        _payPalClient.MakePayment(amount, currency, paymentDetails);
    }
    
    public void ProcessRefund(string transactionId, decimal amount)
    {
        _payPalClient.IssueRefund(transactionId, amount);
    }
}

// Usage - Our application works with any payment processor
public class CheckoutService
{
    private readonly IPaymentProcessor _paymentProcessor;
    
    public CheckoutService(IPaymentProcessor paymentProcessor)
    {
        _paymentProcessor = paymentProcessor;
    }
    
    public void CompletePurchase(decimal amount, string paymentDetails)
    {
        Console.WriteLine("\n=== Processing Checkout ===");
        _paymentProcessor.ProcessPayment(amount, "USD", paymentDetails);
        Console.WriteLine("Checkout completed!");
    }
    
    public void RefundOrder(string transactionId, decimal amount)
    {
        Console.WriteLine("\n=== Processing Refund ===");
        _paymentProcessor.ProcessRefund(transactionId, amount);
        Console.WriteLine("Refund completed!");
    }
}

// Usage demonstration
public class AdapterDemo
{
    public static void Run()
    {
        // Use Stripe
        Console.WriteLine("=== Using Stripe ===");
        var stripeAdapter = new StripeAdapter("sk_test_123");
        var checkout = new CheckoutService(stripeAdapter);
        checkout.CompletePurchase(99.99m, "tok_visa_123");
        checkout.RefundOrder("txn_456", 99.99m);
        
        // Use PayPal - same interface, different implementation
        Console.WriteLine("\n=== Using PayPal ===");
        var payPalAdapter = new PayPalAdapter("client_id_123", "client_secret_456");
        checkout = new CheckoutService(payPalAdapter);
        checkout.CompletePurchase(49.99m, "user@example.com");
        checkout.RefundOrder("PAY-123", 49.99m);
    }
}

1.4 Object Adapter vs Class Adapter (C# Approach)

// Object Adapter (Composition) - Preferred in C#
public class LogAdapter : ILogger
{
    private readonly ThirdPartyLogger _thirdPartyLogger;
    
    public LogAdapter(ThirdPartyLogger logger)
    {
        _thirdPartyLogger = logger;
    }
    
    public void LogInfo(string message)
    {
        _thirdPartyLogger.WriteLog(LogLevel.Info, message);
    }
    
    public void LogError(string message, Exception ex)
    {
        _thirdPartyLogger.WriteLog(LogLevel.Error, $"{message}: {ex.Message}");
    }
}

// Two-Way Adapter - Adapts both sides
public interface IXmlData
{
    string GetXmlData();
}

public interface IJsonData
{
    string GetJsonData();
}

public class XmlToJsonAdapter : IXmlData, IJsonData
{
    private readonly IJsonData _jsonData;
    private readonly IXmlData _xmlData;
    
    public XmlToJsonAdapter(IJsonData jsonData)
    {
        _jsonData = jsonData;
    }
    
    public XmlToJsonAdapter(IXmlData xmlData)
    {
        _xmlData = xmlData;
    }
    
    public string GetXmlData()
    {
        // Convert JSON to XML
        var json = _jsonData.GetJsonData();
        return ConvertJsonToXml(json);
    }
    
    public string GetJsonData()
    {
        // Convert XML to JSON
        var xml = _xmlData.GetXmlData();
        return ConvertXmlToJson(xml);
    }
    
    private string ConvertJsonToXml(string json) => $"{json}";
    private string ConvertXmlToJson(string xml) => xml.Replace("", "").Replace("", "");
}

2. Bridge Pattern

2.1 Understanding Bridge

Definition: The Bridge pattern decouples an abstraction from its implementation so that both can vary independently. It prevents a "cartesian product" complexity explosion.

Real-World Analogy: A remote control (abstraction) and TV device (implementation). Different remotes (basic, advanced) can work with different TV brands (Sony, Samsung) without creating a separate class for each combination.

2.2 ❌ Without Bridge - Class Explosion

// Without Bridge, we'd need a class for every combination
public abstract class RemoteControl { }
public class BasicSonyRemote : RemoteControl { }
public class AdvancedSonyRemote : RemoteControl { }
public class BasicSamsungRemote : RemoteControl { }
public class AdvancedSamsungRemote : RemoteControl { }
// For 2 remote types × 2 TV brands = 4 classes
// For 3 remote types × 5 TV brands = 15 classes! This doesn't scale.

2.3 ✅ Bridge Pattern Implementation

// Implementation hierarchy - Device interfaces
public interface IDevice
{
    void TurnOn();
    void TurnOff();
    void SetVolume(int percent);
    int GetVolume();
    void SetChannel(int channel);
    int GetChannel();
}

// Concrete implementations
public class SonyTV : IDevice
{
    private int _volume = 50;
    private int _channel = 1;
    
    public void TurnOn() => Console.WriteLine("Sony TV: Turning ON");
    public void TurnOff() => Console.WriteLine("Sony TV: Turning OFF");
    
    public void SetVolume(int percent)
    {
        _volume = Math.Clamp(percent, 0, 100);
        Console.WriteLine($"Sony TV: Volume set to {_volume}%");
    }
    
    public int GetVolume() => _volume;
    
    public void SetChannel(int channel)
    {
        _channel = channel;
        Console.WriteLine($"Sony TV: Channel set to {_channel}");
    }
    
    public int GetChannel() => _channel;
}

public class SamsungTV : IDevice
{
    private int _volume = 30;
    private int _channel = 5;
    
    public void TurnOn() => Console.WriteLine("Samsung TV: Power ON");
    public void TurnOff() => Console.WriteLine("Samsung TV: Power OFF");
    
    public void SetVolume(int percent)
    {
        _volume = Math.Clamp(percent, 0, 100);
        Console.WriteLine($"Samsung TV: Volume at {_volume}%");
    }
    
    public int GetVolume() => _volume;
    
    public void SetChannel(int channel)
    {
        _channel = channel;
        Console.WriteLine($"Samsung TV: Channel changed to {_channel}");
    }
    
    public int GetChannel() => _channel;
}

public class LGTV : IDevice
{
    private int _volume = 40;
    private int _channel = 10;
    
    public void TurnOn() => Console.WriteLine("LG TV: Powering up");
    public void TurnOff() => Console.WriteLine("LG TV: Shutting down");
    
    public void SetVolume(int percent)
    {
        _volume = Math.Clamp(percent, 0, 100);
        Console.WriteLine($"LG TV: Volume = {_volume}%");
    }
    
    public int GetVolume() => _volume;
    
    public void SetChannel(int channel)
    {
        _channel = channel;
        Console.WriteLine($"LG TV: Channel {_channel}");
    }
    
    public int GetChannel() => _channel;
}

// Abstraction hierarchy - Remote controls
public abstract class RemoteControl
{
    protected IDevice _device;
    
    protected RemoteControl(IDevice device)
    {
        _device = device;
    }
    
    public abstract void TurnOn();
    public abstract void TurnOff();
    public abstract void SetVolume(int percent);
    public abstract void SetChannel(int channel);
}

// Concrete abstractions
public class BasicRemote : RemoteControl
{
    public BasicRemote(IDevice device) : base(device) { }
    
    public override void TurnOn()
    {
        Console.Write("[BasicRemote] ");
        _device.TurnOn();
    }
    
    public override void TurnOff()
    {
        Console.Write("[BasicRemote] ");
        _device.TurnOff();
    }
    
    public override void SetVolume(int percent)
    {
        Console.Write("[BasicRemote] ");
        _device.SetVolume(percent);
    }
    
    public override void SetChannel(int channel)
    {
        Console.Write("[BasicRemote] ");
        _device.SetChannel(channel);
    }
}

public class AdvancedRemote : RemoteControl
{
    public AdvancedRemote(IDevice device) : base(device) { }
    
    public override void TurnOn()
    {
        Console.Write("[AdvancedRemote] ");
        _device.TurnOn();
    }
    
    public override void TurnOff()
    {
        Console.Write("[AdvancedRemote] ");
        _device.TurnOff();
    }
    
    public override void SetVolume(int percent)
    {
        Console.Write("[AdvancedRemote] ");
        _device.SetVolume(percent);
    }
    
    public override void SetChannel(int channel)
    {
        Console.Write("[AdvancedRemote] ");
        _device.SetChannel(channel);
    }
    
    // Additional advanced features
    public void Mute()
    {
        Console.Write("[AdvancedRemote] Muting - ");
        _device.SetVolume(0);
    }
    
    public void ChannelUp()
    {
        var newChannel = _device.GetChannel() + 1;
        SetChannel(newChannel);
    }
    
    public void ChannelDown()
    {
        var newChannel = _device.GetChannel() - 1;
        SetChannel(newChannel);
    }
    
    public void VolumeUp()
    {
        var newVolume = _device.GetVolume() + 10;
        SetVolume(Math.Min(newVolume, 100));
    }
    
    public void VolumeDown()
    {
        var newVolume = _device.GetVolume() - 10;
        SetVolume(Math.Max(newVolume, 0));
    }
}

public class VoiceControlledRemote : RemoteControl
{
    public VoiceControlledRemote(IDevice device) : base(device) { }
    
    public override void TurnOn()
    {
        Console.Write("[Voice] Alexa, turn on TV - ");
        _device.TurnOn();
    }
    
    public override void TurnOff()
    {
        Console.Write("[Voice] Alexa, turn off TV - ");
        _device.TurnOff();
    }
    
    public override void SetVolume(int percent)
    {
        Console.Write($"[Voice] Set volume to {percent}% - ");
        _device.SetVolume(percent);
    }
    
    public override void SetChannel(int channel)
    {
        Console.Write($"[Voice] Change to channel {channel} - ");
        _device.SetChannel(channel);
    }
    
    public void VoiceCommand(string command)
    {
        if (command.Contains("turn on") || command.Contains("power on"))
            TurnOn();
        else if (command.Contains("turn off") || command.Contains("power off"))
            TurnOff();
        else if (command.Contains("volume up"))
            _device.SetVolume(_device.GetVolume() + 10);
        else if (command.Contains("volume down"))
            _device.SetVolume(_device.GetVolume() - 10);
        else if (command.Contains("channel up"))
            _device.SetChannel(_device.GetChannel() + 1);
        else if (command.Contains("channel down"))
            _device.SetChannel(_device.GetChannel() - 1);
        else if (command.Contains("mute"))
            _device.SetVolume(0);
        else
            Console.WriteLine($"[Voice] Unknown command: {command}");
    }
}

// Usage demonstration
public class BridgeDemo
{
    public static void Run()
    {
        Console.WriteLine("=== Bridge Pattern Demo ===\n");
        
        // Basic remote with Sony TV
        var sonyTV = new SonyTV();
        var basicRemote = new BasicRemote(sonyTV);
        Console.WriteLine("Basic Remote + Sony TV:");
        basicRemote.TurnOn();
        basicRemote.SetChannel(7);
        basicRemote.SetVolume(75);
        
        Console.WriteLine();
        
        // Advanced remote with Samsung TV
        var samsungTV = new SamsungTV();
        var advancedRemote = new AdvancedRemote(samsungTV);
        Console.WriteLine("Advanced Remote + Samsung TV:");
        advancedRemote.TurnOn();
        advancedRemote.ChannelUp();
        advancedRemote.ChannelUp();
        advancedRemote.VolumeUp();
        advancedRemote.Mute();
        
        Console.WriteLine();
        
        // Voice remote with LG TV
        var lgTV = new LGTV();
        var voiceRemote = new VoiceControlledRemote(lgTV);
        Console.WriteLine("Voice Remote + LG TV:");
        voiceRemote.VoiceCommand("turn on");
        voiceRemote.VoiceCommand("channel up");
        voiceRemote.VoiceCommand("volume up");
        voiceRemote.VoiceCommand("mute");
        voiceRemote.VoiceCommand("turn off");
        
        Console.WriteLine("\n--- Flexibility: Switch TV without changing remote ---");
        // Same advanced remote works with different TV
        var anotherRemote = new AdvancedRemote(new LGTV());
        anotherRemote.TurnOn();
        anotherRemote.ChannelUp();
        
        Console.WriteLine("\n--- Flexibility: Switch remote without changing TV ---");
        // Same TV works with different remotes
        var sameTV = new SamsungTV();
        var basic = new BasicRemote(sameTV);
        var advanced = new AdvancedRemote(sameTV);
        basic.TurnOn();
        advanced.Mute();
    }
}

3. Composite Pattern

3.1 Understanding Composite

Definition: The Composite pattern composes objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions uniformly.

Real-World Analogy: A file system. Files (leaf nodes) and folders (composite nodes) both support operations like getSize(), delete(), etc. You can treat both uniformly.

3.2 Composite Pattern Implementation - File System

// Component interface
public interface IFileSystemItem
{
    string Name { get; }
    long GetSize();
    void Display(int indent = 0);
    void Delete();
}

// Leaf node - File
public class File : IFileSystemItem
{
    public string Name { get; }
    public long Size { get; }
    
    public File(string name, long size)
    {
        Name = name;
        Size = size;
    }
    
    public long GetSize() => Size;
    
    public void Display(int indent = 0)
    {
        Console.WriteLine($"{new string(' ', indent)}📄 {Name} ({Size} bytes)");
    }
    
    public void Delete()
    {
        Console.WriteLine($"Deleting file: {Name}");
    }
}

// Composite node - Directory
public class Directory : IFileSystemItem
{
    public string Name { get; }
    private readonly List<IFileSystemItem> _children = new List<IFileSystemItem>();
    
    public Directory(string name)
    {
        Name = name;
    }
    
    public void Add(IFileSystemItem item)
    {
        _children.Add(item);
        Console.WriteLine($"Added {item.Name} to {Name}");
    }
    
    public void Remove(IFileSystemItem item)
    {
        _children.Remove(item);
        Console.WriteLine($"Removed {item.Name} from {Name}");
    }
    
    public IFileSystemItem GetChild(int index)
    {
        return _children[index];
    }
    
    public long GetSize()
    {
        return _children.Sum(child => child.GetSize());
    }
    
    public void Display(int indent = 0)
    {
        Console.WriteLine($"{new string(' ', indent)}📁 {Name}/ (Total: {GetSize()} bytes)");
        foreach (var child in _children)
        {
            child.Display(indent + 2);
        }
    }
    
    public void Delete()
    {
        Console.WriteLine($"Deleting directory: {Name}");
        foreach (var child in _children)
        {
            child.Delete();
        }
        _children.Clear();
    }
    
    public List<IFileSystemItem> Search(string searchTerm)
    {
        var results = new List<IFileSystemItem>();
        
        if (Name.Contains(searchTerm))
            results.Add(this);
        
        foreach (var child in _children)
        {
            if (child is Directory dir)
            {
                results.AddRange(dir.Search(searchTerm));
            }
            else if (child is File file && file.Name.Contains(searchTerm))
            {
                results.Add(file);
            }
        }
        
        return results;
    }
}

// Usage demonstration
public class CompositeDemo
{
    public static void Run()
    {
        Console.WriteLine("=== Composite Pattern - File System Demo ===\n");
        
        // Create files
        var file1 = new File("document.txt", 1024);
        var file2 = new File("image.jpg", 2048);
        var file3 = new File("video.mp4", 10240);
        var file4 = new File("notes.md", 512);
        var file5 = new File("config.json", 256);
        
        // Create directories
        var root = new Directory("root");
        var documents = new Directory("Documents");
        var pictures = new Directory("Pictures");
        var videos = new Directory("Videos");
        
        // Build hierarchy
        root.Add(documents);
        root.Add(pictures);
        root.Add(videos);
        
        documents.Add(file1);
        documents.Add(file4);
        documents.Add(file5);
        
        pictures.Add(file2);
        
        videos.Add(file3);
        
        // Add subdirectory within documents
        var projects = new Directory("Projects");
        documents.Add(projects);
        projects.Add(new File("project1.cs", 4096));
        projects.Add(new File("project2.cs", 8192));
        
        // Display the entire structure
        Console.WriteLine("\n=== File System Structure ===");
        root.Display();
        
        // Calculate total size
        Console.WriteLine($"\nTotal size of root: {root.GetSize()} bytes");
        
        // Search for files
        Console.WriteLine("\n=== Searching for files containing 'project' ===");
        var searchResults = root.Search("project");
        foreach (var item in searchResults)
        {
            Console.WriteLine($"Found: {item.Name}");
        }
        
        // Delete a directory and its contents
        Console.WriteLine("\n=== Deleting Videos directory ===");
        videos.Delete();
        
        Console.WriteLine("\n=== Updated Structure ===");
        root.Display();
    }
}

3.3 Composite Pattern - UI Components

// Component interface
public interface IUIComponent
{
    void Render();
    void Add(IUIComponent component);
    void Remove(IUIComponent component);
    IUIComponent GetChild(int index);
}

// Leaf - Button
public class Button : IUIComponent
{
    public string Text { get; set; }
    
    public Button(string text)
    {
        Text = text;
    }
    
    public void Render()
    {
        Console.WriteLine($"  [Button: {Text}]");
    }
    
    public void Add(IUIComponent component)
    {
        throw new NotSupportedException("Button cannot have children");
    }
    
    public void Remove(IUIComponent component)
    {
        throw new NotSupportedException("Button cannot have children");
    }
    
    public IUIComponent GetChild(int index)
    {
        throw new NotSupportedException("Button cannot have children");
    }
}

// Leaf - TextBox
public class TextBox : IUIComponent
{
    public string Text { get; set; }
    public string Placeholder { get; set; }
    
    public TextBox(string placeholder)
    {
        Placeholder = placeholder;
    }
    
    public void Render()
    {
        Console.WriteLine($"  [TextBox: {Placeholder}] = \"{Text}\"");
    }
    
    public void Add(IUIComponent component)
    {
        throw new NotSupportedException("TextBox cannot have children");
    }
    
    public void Remove(IUIComponent component)
    {
        throw new NotSupportedException("TextBox cannot have children");
    }
    
    public IUIComponent GetChild(int index)
    {
        throw new NotSupportedException("TextBox cannot have children");
    }
}

// Leaf - Label
public class Label : IUIComponent
{
    public string Text { get; set; }
    
    public Label(string text)
    {
        Text = text;
    }
    
    public void Render()
    {
        Console.WriteLine($"  [Label: {Text}]");
    }
    
    public void Add(IUIComponent component)
    {
        throw new NotSupportedException("Label cannot have children");
    }
    
    public void Remove(IUIComponent component)
    {
        throw new NotSupportedException("Label cannot have children");
    }
    
    public IUIComponent GetChild(int index)
    {
        throw new NotSupportedException("Label cannot have children");
    }
}

// Composite - Panel
public class Panel : IUIComponent
{
    public string Name { get; set; }
    private readonly List<IUIComponent> _children = new List<IUIComponent>();
    
    public Panel(string name)
    {
        Name = name;
    }
    
    public void Render()
    {
        Console.WriteLine($"+ Panel: {Name}");
        foreach (var child in _children)
        {
            child.Render();
        }
        Console.WriteLine($"- End Panel: {Name}");
    }
    
    public void Add(IUIComponent component)
    {
        _children.Add(component);
    }
    
    public void Remove(IUIComponent component)
    {
        _children.Remove(component);
    }
    
    public IUIComponent GetChild(int index)
    {
        return _children[index];
    }
}

// Composite - Form
public class Form : IUIComponent
{
    public string Title { get; set; }
    private readonly List<IUIComponent> _children = new List<IUIComponent>();
    
    public Form(string title)
    {
        Title = title;
    }
    
    public void Render()
    {
        Console.WriteLine($"\n╔════════════════════════════╗");
        Console.WriteLine($"║  {Title.PadRight(26)}║");
        Console.WriteLine($"╠════════════════════════════╣");
        foreach (var child in _children)
        {
            child.Render();
        }
        Console.WriteLine($"╚════════════════════════════╝");
    }
    
    public void Add(IUIComponent component)
    {
        _children.Add(component);
    }
    
    public void Remove(IUIComponent component)
    {
        _children.Remove(component);
    }
    
    public IUIComponent GetChild(int index)
    {
        return _children[index];
    }
}

// Usage
public class UIDemo
{
    public static void Run()
    {
        Console.WriteLine("=== Composite Pattern - UI Components ===\n");
        
        // Create form
        var loginForm = new Form("Login");
        
        // Create panels
        var headerPanel = new Panel("Header");
        var mainPanel = new Panel("Main Content");
        var footerPanel = new Panel("Footer");
        
        // Build header
        headerPanel.Add(new Label("Welcome to MyApp"));
        
        // Build main content
        mainPanel.Add(new Label("Username:"));
        mainPanel.Add(new TextBox("Enter username"));
        mainPanel.Add(new Label("Password:"));
        mainPanel.Add(new TextBox("Enter password"));
        mainPanel.Add(new Button("Login"));
        
        // Build footer
        footerPanel.Add(new Label("Forgot password? Contact support"));
        
        // Assemble form
        loginForm.Add(headerPanel);
        loginForm.Add(mainPanel);
        loginForm.Add(footerPanel);
        
        // Render entire UI
        loginForm.Render();
        
        // Add a new panel dynamically
        Console.WriteLine("\n--- Adding Registration Link Panel ---");
        var registerPanel = new Panel("Registration Link");
        registerPanel.Add(new Button("Create New Account"));
        loginForm.Add(registerPanel);
        loginForm.Render();
    }
}

4. Decorator Pattern

4.1 Understanding Decorator

Definition: The Decorator pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

Real-World Analogy: Pizza toppings. You start with a base pizza and can add toppings (decorators) like cheese, mushrooms, pepperoni. Each topping adds cost and description without modifying the base pizza class.

4.2 Decorator Pattern Implementation - Pizza Ordering

// Component interface
public interface IPizza
{
    string GetDescription();
    decimal GetCost();
}

// Concrete component - Base pizza
public class MargheritaPizza : IPizza
{
    public string GetDescription() => "Margherita Pizza";
    public decimal GetCost() => 8.99m;
}

public class PepperoniPizza : IPizza
{
    public string GetDescription() => "Pepperoni Pizza";
    public decimal GetCost() => 10.99m;
}

public class VegetarianPizza : IPizza
{
    public string GetDescription() => "Vegetarian Pizza";
    public decimal GetCost() => 9.99m;
}

// Base decorator
public abstract class PizzaDecorator : IPizza
{
    protected IPizza _pizza;
    
    protected PizzaDecorator(IPizza pizza)
    {
        _pizza = pizza;
    }
    
    public virtual string GetDescription() => _pizza.GetDescription();
    public virtual decimal GetCost() => _pizza.GetCost();
}

// Concrete decorators - Toppings
public class CheeseDecorator : PizzaDecorator
{
    public CheeseDecorator(IPizza pizza) : base(pizza) { }
    
    public override string GetDescription() => $"{_pizza.GetDescription()}, Extra Cheese";
    public override decimal GetCost() => _pizza.GetCost() + 1.50m;
}

public class MushroomDecorator : PizzaDecorator
{
    public MushroomDecorator(IPizza pizza) : base(pizza) { }
    
    public override string GetDescription() => $"{_pizza.GetDescription()}, Mushrooms";
    public override decimal GetCost() => _pizza.GetCost() + 2.00m;
}

public class PepperoniDecorator : PizzaDecorator
{
    public PepperoniDecorator(IPizza pizza) : base(pizza) { }
    
    public override string GetDescription() => $"{_pizza.GetDescription()}, Pepperoni";
    public override decimal GetCost() => _pizza.GetCost() + 2.50m;
}

public class OlivesDecorator : PizzaDecorator
{
    public OlivesDecorator(IPizza pizza) : base(pizza) { }
    
    public override string GetDescription() => $"{_pizza.GetDescription()}, Olives";
    public override decimal GetCost() => _pizza.GetCost() + 1.75m;
}

public class ExtraSauceDecorator : PizzaDecorator
{
    public ExtraSauceDecorator(IPizza pizza) : base(pizza) { }
    
    public override string GetDescription() => $"{_pizza.GetDescription()}, Extra Sauce";
    public override decimal GetCost() => _pizza.GetCost() + 1.00m;
}

// Usage
public class PizzaDemo
{
    public static void Run()
    {
        Console.WriteLine("=== Decorator Pattern - Pizza Ordering ===\n");
        
        // Order 1: Margherita with cheese and mushrooms
        IPizza pizza1 = new MargheritaPizza();
        pizza1 = new CheeseDecorator(pizza1);
        pizza1 = new MushroomDecorator(pizza1);
        
        Console.WriteLine($"Order 1: {pizza1.GetDescription()}");
        Console.WriteLine($"Cost: ${pizza1.GetCost():F2}\n");
        
        // Order 2: Pepperoni with extra cheese and pepperoni
        IPizza pizza2 = new PepperoniPizza();
        pizza2 = new CheeseDecorator(pizza2);
        pizza2 = new PepperoniDecorator(pizza2);
        pizza2 = new ExtraSauceDecorator(pizza2);
        
        Console.WriteLine($"Order 2: {pizza2.GetDescription()}");
        Console.WriteLine($"Cost: ${pizza2.GetCost():F2}\n");
        
        // Order 3: Vegetarian with multiple toppings
        IPizza pizza3 = new VegetarianPizza();
        pizza3 = new MushroomDecorator(pizza3);
        pizza3 = new OlivesDecorator(pizza3);
        pizza3 = new CheeseDecorator(pizza3);
        pizza3 = new ExtraSauceDecorator(pizza3);
        
        Console.WriteLine($"Order 3: {pizza3.GetDescription()}");
        Console.WriteLine($"Cost: ${pizza3.GetCost():F2}");
    }
}

4.3 Decorator Pattern - .NET Middleware (ASP.NET Core Style)

// Request delegate
public delegate Task RequestDelegate(HttpContext context);

// Middleware interface
public interface IMiddleware
{
    Task InvokeAsync(HttpContext context, RequestDelegate next);
}

// Base decorator for middleware
public abstract class MiddlewareDecorator : IMiddleware
{
    protected readonly IMiddleware _next;
    
    protected MiddlewareDecorator(IMiddleware next)
    {
        _next = next;
    }
    
    public abstract Task InvokeAsync(HttpContext context, RequestDelegate next);
}

// Concrete middleware - Logging
public class LoggingMiddleware : IMiddleware
{
    private readonly ILogger _logger;
    
    public LoggingMiddleware(ILogger logger)
    {
        _logger = logger;
    }
    
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        _logger.Log($"Request: {context.Request.Method} {context.Request.Path}");
        await next(context);
        _logger.Log($"Response: {context.Response.StatusCode}");
    }
}

// Concrete middleware - Authentication
public class AuthenticationMiddleware : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        if (!context.Request.Headers.ContainsKey("Authorization"))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Unauthorized");
            return;
        }
        
        await next(context);
    }
}

// Concrete middleware - Caching
public class CachingMiddleware : IMiddleware
{
    private readonly ICache _cache;
    
    public CachingMiddleware(ICache cache)
    {
        _cache = cache;
    }
    
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        var cacheKey = context.Request.Path;
        var cachedResponse = _cache.Get(cacheKey);
        
        if (cachedResponse != null)
        {
            await context.Response.WriteAsync(cachedResponse);
            return;
        }
        
        // Capture response
        var originalBody = context.Response.Body;
        using var memoryStream = new MemoryStream();
        context.Response.Body = memoryStream;
        
        await next(context);
        
        // Cache response
        memoryStream.Position = 0;
        var response = await new StreamReader(memoryStream).ReadToEndAsync();
        _cache.Set(cacheKey, response, TimeSpan.FromMinutes(5));
        
        memoryStream.Position = 0;
        await memoryStream.CopyToAsync(originalBody);
    }
}

// Application builder (simplified)
public class ApplicationBuilder
{
    private IMiddleware _firstMiddleware;
    private readonly List<Type> _middlewareTypes = new List<Type>();
    
    public ApplicationBuilder Use<T>() where T : IMiddleware
    {
        _middlewareTypes.Add(typeof(T));
        return this;
    }
    
    public RequestDelegate Build(IServiceProvider services)
    {
        // Build chain of middleware
        RequestDelegate finalDelegate = context => Task.CompletedTask;
        
        for (int i = _middlewareTypes.Count - 1; i >= 0; i--)
        {
            var middlewareType = _middlewareTypes[i];
            var middleware = (IMiddleware)ActivatorUtilities.CreateInstance(services, middlewareType);
            var next = finalDelegate;
            finalDelegate = context => middleware.InvokeAsync(context, next);
        }
        
        return finalDelegate;
    }
}

// Usage simulation
public class HttpContext
{
    public HttpRequest Request { get; } = new HttpRequest();
    public HttpResponse Response { get; } = new HttpResponse();
}

public class HttpRequest
{
    public string Method { get; set; } = "GET";
    public string Path { get; set; } = "/";
    public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>();
}

public class HttpResponse
{
    public int StatusCode { get; set; } = 200;
    public MemoryStream Body { get; set; } = new MemoryStream();
    
    public async Task WriteAsync(string content)
    {
        var bytes = Encoding.UTF8.GetBytes(content);
        await Body.WriteAsync(bytes, 0, bytes.Length);
    }
}

public interface ILogger
{
    void Log(string message);
}

public interface ICache
{
    string Get(string key);
    void Set(string key, string value, TimeSpan expiry);
}

5. Facade Pattern

5.1 Understanding Facade

Definition: The Facade pattern provides a simplified interface to a complex subsystem. It hides the complexity of the subsystem and makes it easier to use.

Real-World Analogy: A restaurant's waiter. You don't interact directly with the kitchen, the chef, the inventory, or the billing system. The waiter provides a simple interface for placing orders and receiving food.

5.2 Facade Pattern Implementation - E-Commerce System

// Complex subsystems
public class InventorySystem
{
    private Dictionary<int, int> _stock = new Dictionary<int, int>
    {
        [101] = 10,  // Product 101: 10 units
        [102] = 5,   // Product 102: 5 units
        [103] = 0    // Product 103: Out of stock
    };
    
    public bool CheckAvailability(int productId, int quantity)
    {
        if (_stock.ContainsKey(productId))
        {
            var available = _stock[productId] >= quantity;
            Console.WriteLine($"Inventory: Product {productId} - {(available ? "Available" : "Out of stock")}");
            return available;
        }
        return false;
    }
    
    public void ReserveStock(int productId, int quantity)
    {
        if (_stock.ContainsKey(productId))
        {
            _stock[productId] -= quantity;
            Console.WriteLine($"Inventory: Reserved {quantity} units of product {productId}");
        }
    }
}

public class PaymentSystem
{
    public bool ProcessPayment(string cardNumber, decimal amount)
    {
        Console.WriteLine($"Payment: Processing {amount:C} with card ending in {cardNumber.Substring(cardNumber.Length - 4)}");
        // Simulate payment processing
        return true;
    }
    
    public void RefundPayment(string transactionId, decimal amount)
    {
        Console.WriteLine($"Payment: Refunded {amount:C} for transaction {transactionId}");
    }
}

public class ShippingSystem
{
    public string CreateShippingLabel(int orderId, string address)
    {
        var trackingNumber = $"TRK-{orderId}-{DateTime.Now.Ticks}";
        Console.WriteLine($"Shipping: Created label for order {orderId} to {address}");
        Console.WriteLine($"Shipping: Tracking number: {trackingNumber}");
        return trackingNumber;
    }
    
    public void SchedulePickup(string trackingNumber, DateTime pickupDate)
    {
        Console.WriteLine($"Shipping: Scheduled pickup for {trackingNumber} on {pickupDate:d}");
    }
}

public class NotificationSystem
{
    public void SendEmail(string email, string subject, string message)
    {
        Console.WriteLine($"Email: To: {email}, Subject: {subject}");
        Console.WriteLine($"Email: {message}\n");
    }
    
    public void SendSms(string phoneNumber, string message)
    {
        Console.WriteLine($"SMS: To: {phoneNumber}, Message: {message}");
    }
}

public class CustomerSystem
{
    private Dictionary<int, Customer> _customers = new Dictionary<int, Customer>();
    
    public CustomerSystem()
    {
        _customers[1] = new Customer { Id = 1, Name = "John Doe", Email = "john@example.com", Phone = "555-1234", Address = "123 Main St" };
        _customers[2] = new Customer { Id = 2, Name = "Jane Smith", Email = "jane@example.com", Phone = "555-5678", Address = "456 Oak Ave" };
    }
    
    public Customer GetCustomer(int customerId)
    {
        return _customers.ContainsKey(customerId) ? _customers[customerId] : null;
    }
}

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

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

public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public List<OrderItem> Items { get; set; } = new List<OrderItem>();
    public decimal TotalAmount { get; set; }
    public string Status { get; set; } = "Pending";
    public string TrackingNumber { get; set; }
    public string TransactionId { get; set; }
}

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

// Facade - Simplifies the complex ordering process
public class OrderFacade
{
    private readonly InventorySystem _inventory;
    private readonly PaymentSystem _payment;
    private readonly ShippingSystem _shipping;
    private readonly NotificationSystem _notification;
    private readonly CustomerSystem _customerSystem;
    private static int _nextOrderId = 1000;
    
    public OrderFacade()
    {
        _inventory = new InventorySystem();
        _payment = new PaymentSystem();
        _shipping = new ShippingSystem();
        _notification = new NotificationSystem();
        _customerSystem = new CustomerSystem();
    }
    
    public OrderResult PlaceOrder(int customerId, List<OrderItemRequest> items, string cardNumber)
    {
        Console.WriteLine("\n=== Starting Order Processing ===");
        
        var result = new OrderResult();
        
        // Step 1: Get customer
        var customer = _customerSystem.GetCustomer(customerId);
        if (customer == null)
        {
            result.Success = false;
            result.Message = "Customer not found";
            return result;
        }
        
        // Step 2: Check inventory for all items
        foreach (var item in items)
        {
            if (!_inventory.CheckAvailability(item.ProductId, item.Quantity))
            {
                result.Success = false;
                result.Message = $"Product {item.ProductId} is out of stock";
                return result;
            }
        }
        
        // Step 3: Calculate total
        decimal total = 0;
        var orderItems = new List<OrderItem>();
        foreach (var item in items)
        {
            var product = GetProduct(item.ProductId);
            var orderItem = new OrderItem
            {
                ProductId = item.ProductId,
                Quantity = item.Quantity,
                UnitPrice = product.Price
            };
            orderItems.Add(orderItem);
            total += product.Price * item.Quantity;
        }
        
        // Step 4: Process payment
        if (!_payment.ProcessPayment(cardNumber, total))
        {
            result.Success = false;
            result.Message = "Payment failed";
            return result;
        }
        
        // Step 5: Create order
        var order = new Order
        {
            Id = _nextOrderId++,
            CustomerId = customerId,
            Items = orderItems,
            TotalAmount = total,
            TransactionId = $"TXN-{DateTime.Now.Ticks}"
        };
        
        // Step 6: Reserve inventory
        foreach (var item in items)
        {
            _inventory.ReserveStock(item.ProductId, item.Quantity);
        }
        
        // Step 7: Create shipping label
        var trackingNumber = _shipping.CreateShippingLabel(order.Id, customer.Address);
        order.TrackingNumber = trackingNumber;
        
        // Step 8: Send notifications
        _notification.SendEmail(
            customer.Email,
            $"Order Confirmation #{order.Id}",
            $"Thank you for your order! Total: {total:C}\nTracking: {trackingNumber}"
        );
        
        _notification.SendSms(
            customer.Phone,
            $"Order #{order.Id} confirmed! Total: {total:C}"
        );
        
        result.Success = true;
        result.OrderId = order.Id;
        result.TrackingNumber = trackingNumber;
        result.TotalAmount = total;
        result.Message = "Order placed successfully";
        
        Console.WriteLine("=== Order Processing Complete ===\n");
        return result;
    }
    
    private Product GetProduct(int productId)
    {
        // Simulate product lookup
        return productId switch
        {
            101 => new Product { Id = 101, Name = "Laptop", Price = 999.99m },
            102 => new Product { Id = 102, Name = "Mouse", Price = 29.99m },
            103 => new Product { Id = 103, Name = "Keyboard", Price = 79.99m },
            _ => new Product { Id = productId, Name = "Unknown", Price = 0 }
        };
    }
    
    public OrderStatusResult CheckOrderStatus(int orderId)
    {
        Console.WriteLine($"Checking status for order #{orderId}");
        // Simulate status check
        return new OrderStatusResult
        {
            OrderId = orderId,
            Status = "Shipped",
            TrackingNumber = $"TRK-{orderId}-12345",
            EstimatedDelivery = DateTime.Now.AddDays(3)
        };
    }
    
    public bool CancelOrder(int orderId)
    {
        Console.WriteLine($"Cancelling order #{orderId}");
        // Simulate cancellation
        return true;
    }
}

// DTOs
public class OrderItemRequest
{
    public int ProductId { get; set; }
    public int Quantity { get; set; }
}

public class OrderResult
{
    public bool Success { get; set; }
    public int OrderId { get; set; }
    public string TrackingNumber { get; set; }
    public decimal TotalAmount { get; set; }
    public string Message { get; set; }
}

public class OrderStatusResult
{
    public int OrderId { get; set; }
    public string Status { get; set; }
    public string TrackingNumber { get; set; }
    public DateTime EstimatedDelivery { get; set; }
}

// Usage - Client code sees only the simple facade
public class FacadeDemo
{
    public static void Run()
    {
        Console.WriteLine("=== Facade Pattern - E-Commerce Order System ===\n");
        
        var orderFacade = new OrderFacade();
        
        // Simple interface - client doesn't need to know about inventory, payment, shipping, etc.
        var items = new List<OrderItemRequest>
        {
            new OrderItemRequest { ProductId = 101, Quantity = 1 },  // Laptop
            new OrderItemRequest { ProductId = 102, Quantity = 2 }   // Mouse x2
        };
        
        var result = orderFacade.PlaceOrder(1, items, "4111111111111111");
        
        if (result.Success)
        {
            Console.WriteLine($"✅ Order #{result.OrderId} placed successfully!");
            Console.WriteLine($"📦 Tracking: {result.TrackingNumber}");
            Console.WriteLine($"💰 Total: {result.TotalAmount:C}");
        }
        else
        {
            Console.WriteLine($"❌ Order failed: {result.Message}");
        }
        
        // Check order status
        Console.WriteLine("\n--- Checking Order Status ---");
        var status = orderFacade.CheckOrderStatus(1001);
        Console.WriteLine($"Order #{status.OrderId}: {status.Status}");
        Console.WriteLine($"Tracking: {status.TrackingNumber}");
        Console.WriteLine($"Estimated Delivery: {status.EstimatedDelivery:d}");
    }
}

6. Flyweight Pattern

6.1 Understanding Flyweight

Definition: The Flyweight pattern reduces memory usage by sharing as much data as possible with similar objects. It's useful when you need to create a large number of similar objects.

Real-World Analogy: In a word processor, each character is a flyweight object. The character's appearance (font, size, style) is shared, while position is extrinsic and stored separately.

6.2 Flyweight Pattern Implementation - Text Editor

// Flyweight - Shared intrinsic state
public class CharacterGlyph
{
    public char Character { get; }
    public string FontFamily { get; }
    public int FontSize { get; }
    public bool IsBold { get; }
    public bool IsItalic { get; }
    public ConsoleColor Color { get; }
    
    public CharacterGlyph(char character, string fontFamily, int fontSize, bool isBold, bool isItalic, ConsoleColor color)
    {
        Character = character;
        FontFamily = fontFamily;
        FontSize = fontSize;
        IsBold = isBold;
        IsItalic = isItalic;
        Color = color;
    }
    
    public void Display(int x, int y)
    {
        Console.ForegroundColor = Color;
        if (IsBold) Console.Write($"\u001b[1m");
        if (IsItalic) Console.Write($"\u001b[3m");
        
        Console.SetCursorPosition(x, y);
        Console.Write(Character);
        
        // Reset formatting
        Console.ResetColor();
        if (IsBold || IsItalic) Console.Write($"\u001b[0m");
    }
    
    public override string ToString()
    {
        return $"{Character} ({FontFamily}, {FontSize}pt{(IsBold ? ", Bold" : "")}{(IsItalic ? ", Italic" : "")})";
    }
}

// Flyweight Factory - Manages and reuses flyweights
public class CharacterGlyphFactory
{
    private Dictionary<string, CharacterGlyph> _glyphs = new Dictionary<string, CharacterGlyph>();
    
    public CharacterGlyph GetGlyph(char character, string fontFamily, int fontSize, bool isBold, bool isItalic, ConsoleColor color)
    {
        // Create a unique key based on intrinsic properties
        string key = $"{character}|{fontFamily}|{fontSize}|{isBold}|{isItalic}|{color}";
        
        if (!_glyphs.ContainsKey(key))
        {
            _glyphs[key] = new CharacterGlyph(character, fontFamily, fontSize, isBold, isItalic, color);
            Console.WriteLine($"Created new glyph: {_glyphs[key]}");
        }
        
        return _glyphs[key];
    }
    
    public int GetGlyphCount() => _glyphs.Count;
}

// Context - Extrinsic state for each character instance
public class CharacterPosition
{
    public CharacterGlyph Glyph { get; }
    public int X { get; set; }
    public int Y { get; set; }
    
    public CharacterPosition(CharacterGlyph glyph, int x, int y)
    {
        Glyph = glyph;
        X = x;
        Y = y;
    }
    
    public void Display()
    {
        Glyph.Display(X, Y);
    }
}

// Document class that uses flyweights
public class Document
{
    private List<CharacterPosition> _characters = new List<CharacterPosition>();
    private CharacterGlyphFactory _factory = new CharacterGlyphFactory();
    
    public void AddCharacter(char character, int x, int y, string fontFamily = "Arial", int fontSize = 12, 
                            bool isBold = false, bool isItalic = false, ConsoleColor color = ConsoleColor.White)
    {
        var glyph = _factory.GetGlyph(character, fontFamily, fontSize, isBold, isItalic, color);
        _characters.Add(new CharacterPosition(glyph, x, y));
    }
    
    public void Display()
    {
        Console.Clear();
        foreach (var character in _characters)
        {
            character.Display();
        }
        Console.SetCursorPosition(0, Console.WindowHeight - 1);
        Console.WriteLine($"\nTotal characters: {_characters.Count}");
        Console.WriteLine($"Unique glyphs: {_factory.GetGlyphCount()}");
        Console.WriteLine($"Memory saved: {(_characters.Count - _factory.GetGlyphCount()) * 100 / _characters.Count}%");
    }
}

// Tree example - Another common Flyweight use case
public class TreeType
{
    public string Name { get; }
    public ConsoleColor Color { get; }
    public string Texture { get; }
    
    public TreeType(string name, ConsoleColor color, string texture)
    {
        Name = name;
        Color = color;
        Texture = texture;
    }
    
    public void Draw(int x, int y)
    {
        Console.ForegroundColor = Color;
        Console.SetCursorPosition(x, y);
        Console.Write($"🌳");
        Console.ResetColor();
    }
}

public class TreeFactory
{
    private Dictionary<string, TreeType> _treeTypes = new Dictionary<string, TreeType>();
    
    public TreeType GetTreeType(string name, ConsoleColor color, string texture)
    {
        string key = $"{name}|{color}|{texture}";
        
        if (!_treeTypes.ContainsKey(key))
        {
            _treeTypes[key] = new TreeType(name, color, texture);
            Console.WriteLine($"Created new tree type: {name}");
        }
        
        return _treeTypes[key];
    }
    
    public int GetTreeTypeCount() => _treeTypes.Count;
}

public class Tree
{
    public TreeType Type { get; }
    public int X { get; }
    public int Y { get; }
    
    public Tree(TreeType type, int x, int y)
    {
        Type = type;
        X = x;
        Y = y;
    }
    
    public void Draw()
    {
        Type.Draw(X, Y);
    }
}

public class Forest
{
    private List<Tree> _trees = new List<Tree>();
    private TreeFactory _factory = new TreeFactory();
    
    public void PlantTree(int x, int y, string name, ConsoleColor color, string texture)
    {
        var type = _factory.GetTreeType(name, color, texture);
        _trees.Add(new Tree(type, x, y));
    }
    
    public void Draw()
    {
        Console.Clear();
        foreach (var tree in _trees)
        {
            tree.Draw();
        }
        Console.SetCursorPosition(0, Console.WindowHeight - 2);
        Console.WriteLine($"Total trees: {_trees.Count}");
        Console.WriteLine($"Unique tree types: {_factory.GetTreeTypeCount()}");
    }
}

// Usage
public class FlyweightDemo
{
    public static void Run()
    {
        Console.WriteLine("=== Flyweight Pattern - Document Editor ===\n");
        
        var doc = new Document();
        
        // Add text with shared glyphs
        string text = "Hello World! This is a flyweight pattern demonstration.";
        
        for (int i = 0; i < text.Length; i++)
        {
            // Different styles for demonstration
            bool isBold = i % 10 == 0;
            bool isItalic = i % 15 == 0;
            var color = i % 3 == 0 ? ConsoleColor.Red : (i % 3 == 1 ? ConsoleColor.Green : ConsoleColor.White);
            
            doc.AddCharacter(text[i], i * 2, 5, "Arial", 12, isBold, isItalic, color);
        }
        
        doc.Display();
        
        Console.WriteLine("\n\n=== Flyweight Pattern - Forest Simulation ===\n");
        
        var forest = new Forest();
        
        // Plant many trees with only a few shared types
        Random rand = new Random();
        string[] treeNames = { "Oak", "Pine", "Maple", "Birch" };
        ConsoleColor[] colors = { ConsoleColor.Green, ConsoleColor.DarkGreen, ConsoleColor.Yellow, ConsoleColor.DarkYellow };
        
        for (int i = 0; i < 1000; i++)
        {
            int x = rand.Next(0, 80);
            int y = rand.Next(0, 20);
            int typeIndex = rand.Next(0, treeNames.Length);
            
            forest.PlantTree(x, y, treeNames[typeIndex], colors[typeIndex], "leaf_texture");
        }
        
        forest.Draw();
        
        Console.WriteLine("\nMemory saved by sharing tree types!");
    }
}

7. Proxy Pattern

7.1 Understanding Proxy

Definition: The Proxy pattern provides a surrogate or placeholder for another object to control access to it. Proxies can be used for lazy loading, access control, logging, caching, etc.

Real-World Analogy: A credit card is a proxy for a bank account. You use the card to make purchases, and the card handles the communication with the bank, adding security and convenience.

7.2 Proxy Pattern Implementation

// Subject interface
public interface IImage
{
    void Display();
    string GetFilename();
    long GetSize();
}

// Real subject - Expensive to create
public class HighResolutionImage : IImage
{
    private string _filename;
    private byte[] _imageData;
    private long _size;
    
    public HighResolutionImage(string filename)
    {
        _filename = filename;
        LoadImageFromDisk();
    }
    
    private void LoadImageFromDisk()
    {
        Console.WriteLine($"Loading high-resolution image: {_filename} (This is expensive!)");
        // Simulate loading large image
        Thread.Sleep(2000);
        _imageData = new byte[10_000_000]; // Simulate 10MB image
        _size = _imageData.Length;
        Console.WriteLine($"Image loaded: {_filename} ({_size / 1_000_000}MB)");
    }
    
    public void Display()
    {
        Console.WriteLine($"Displaying image: {_filename}");
    }
    
    public string GetFilename() => _filename;
    
    public long GetSize() => _size;
}

// Virtual Proxy - Lazy loading
public class ImageProxy : IImage
{
    private string _filename;
    private HighResolutionImage _realImage;
    private bool _isLoading;
    
    public ImageProxy(string filename)
    {
        _filename = filename;
        Console.WriteLine($"Proxy created for: {_filename}");
    }
    
    public void Display()
    {
        if (_realImage == null && !_isLoading)
        {
            Console.WriteLine($"Proxy: Loading image on demand...");
            _isLoading = true;
            
            // Simulate async loading
            Task.Run(() =>
            {
                _realImage = new HighResolutionImage(_filename);
                _realImage.Display();
                _isLoading = false;
            });
            
            Console.WriteLine($"Proxy: Displaying placeholder while loading...");
            DisplayPlaceholder();
        }
        else if (_realImage != null)
        {
            _realImage.Display();
        }
        else
        {
            Console.WriteLine($"Proxy: Image still loading, please wait...");
            DisplayPlaceholder();
        }
    }
    
    private void DisplayPlaceholder()
    {
        Console.WriteLine($"📷 [Placeholder] {_filename} - Loading...");
    }
    
    public string GetFilename() => _filename;
    
    public long GetSize()
    {
        if (_realImage != null)
            return _realImage.GetSize();
        else
            return 0; // Not loaded yet
    }
}

// Protection Proxy - Access control
public interface IDocument
{
    void Display();
    void Edit(string content);
    string GetContent();
}

public class Document : IDocument
{
    private string _filename;
    private string _content;
    private string _owner;
    
    public Document(string filename, string owner)
    {
        _filename = filename;
        _owner = owner;
        LoadDocument();
    }
    
    private void LoadDocument()
    {
        Console.WriteLine($"Loading document: {_filename}");
        Thread.Sleep(1000);
        _content = $"Content of {_filename}";
    }
    
    public void Display()
    {
        Console.WriteLine($"Document: {_filename}");
        Console.WriteLine($"Content: {_content}");
    }
    
    public void Edit(string content)
    {
        _content = content;
        Console.WriteLine($"Document edited: {_filename}");
    }
    
    public string GetContent() => _content;
    public string GetOwner() => _owner;
}

public class DocumentProxy : IDocument
{
    private Document _realDocument;
    private string _currentUser;
    
    public DocumentProxy(string filename, string owner, string currentUser)
    {
        _realDocument = new Document(filename, owner);
        _currentUser = currentUser;
    }
    
    public void Display()
    {
        // Anyone can view documents
        Console.WriteLine($"\n[Proxy] User '{_currentUser}' is viewing document");
        _realDocument.Display();
    }
    
    public void Edit(string content)
    {
        // Only owner can edit
        if (_currentUser == _realDocument.GetOwner())
        {
            Console.WriteLine($"\n[Proxy] User '{_currentUser}' is editing document");
            _realDocument.Edit(content);
        }
        else
        {
            Console.WriteLine($"\n[Proxy] Access Denied: User '{_currentUser}' cannot edit this document");
            Console.WriteLine($"Only owner '{_realDocument.GetOwner()}' can edit");
        }
    }
    
    public string GetContent()
    {
        // Anyone can read content
        return _realDocument.GetContent();
    }
}

// Cache Proxy - Caching results
public interface IDataService
{
    Task<string> GetDataAsync(string query);
}

public class RealDataService : IDataService
{
    public async Task<string> GetDataAsync(string query)
    {
        Console.WriteLine($"RealDataService: Executing expensive query: {query}");
        await Task.Delay(2000); // Simulate database query
        return $"Result for '{query}' at {DateTime.Now:T}";
    }
}

public class CachingDataServiceProxy : IDataService
{
    private readonly IDataService _realService;
    private readonly Dictionary<string, (string Result, DateTime Expiry)> _cache = new();
    private readonly TimeSpan _cacheDuration = TimeSpan.FromSeconds(10);
    
    public CachingDataServiceProxy(IDataService realService)
    {
        _realService = realService;
    }
    
    public async Task<string> GetDataAsync(string query)
    {
        // Check cache
        if (_cache.ContainsKey(query) && _cache[query].Expiry > DateTime.Now)
        {
            Console.WriteLine($"CacheProxy: Returning cached result for '{query}'");
            return _cache[query].Result;
        }
        
        // Get from real service
        Console.WriteLine($"CacheProxy: Cache miss for '{query}', calling real service");
        var result = await _realService.GetDataAsync(query);
        
        // Store in cache
        _cache[query] = (result, DateTime.Now + _cacheDuration);
        
        return result;
    }
    
    public void ClearCache()
    {
        _cache.Clear();
        Console.WriteLine("CacheProxy: Cache cleared");
    }
}

// Logging Proxy - Adds logging
public class LoggingProxy<T> : DispatchProxy where T : class
{
    private T _decorated;
    private ILogger _logger;
    
    public static T Create(T decorated, ILogger logger)
    {
        object proxy = Create<T, LoggingProxy<T>>();
        ((LoggingProxy<T>)proxy).SetParameters(decorated, logger);
        return (T)proxy;
    }
    
    private void SetParameters(T decorated, ILogger logger)
    {
        _decorated = decorated;
        _logger = logger;
    }
    
    protected override object Invoke(MethodInfo targetMethod, object[] args)
    {
        _logger.Log($"Calling {targetMethod.Name} with args: {string.Join(", ", args)}");
        
        var stopwatch = System.Diagnostics.Stopwatch.StartNew();
        
        try
        {
            var result = targetMethod.Invoke(_decorated, args);
            stopwatch.Stop();
            
            _logger.Log($"{targetMethod.Name} completed in {stopwatch.ElapsedMilliseconds}ms");
            return result;
        }
        catch (Exception ex)
        {
            _logger.Log($"{targetMethod.Name} failed: {ex.Message}");
            throw;
        }
    }
}

// Usage
public class ProxyDemo
{
    public static async Task RunAsync()
    {
        Console.WriteLine("=== Proxy Pattern Demo ===\n");
        
        // 1. Virtual Proxy - Lazy Loading
        Console.WriteLine("--- Virtual Proxy (Lazy Image Loading) ---");
        IImage image = new ImageProxy("vacation_photo.jpg");
        
        Console.WriteLine("\nImage created, not loaded yet");
        Console.WriteLine($"Filename: {image.GetFilename()}");
        Console.WriteLine($"Size: {image.GetSize()} bytes (not loaded)");
        
        Console.WriteLine("\nFirst display call - will load image:");
        image.Display();
        
        Console.WriteLine("\nWaiting for image to load...");
        Thread.Sleep(2500);
        
        Console.WriteLine("\nSecond display call - image already loaded:");
        image.Display();
        
        // 2. Protection Proxy - Access Control
        Console.WriteLine("\n\n--- Protection Proxy (Access Control) ---");
        
        var docProxy = new DocumentProxy("confidential.txt", "admin", "john");
        docProxy.Display();
        docProxy.Edit("New content by john");
        
        Console.WriteLine();
        
        var docProxy2 = new DocumentProxy("confidential.txt", "admin", "admin");
        docProxy2.Display();
        docProxy2.Edit("New content by admin");
        
        // 3. Cache Proxy
        Console.WriteLine("\n\n--- Cache Proxy ---");
        var realService = new RealDataService();
        var cachedService = new CachingDataServiceProxy(realService);
        
        Console.WriteLine("First call:");
        var result1 = await cachedService.GetDataAsync("SELECT * FROM Users");
        Console.WriteLine($"Result: {result1}");
        
        Console.WriteLine("\nSecond call (should be cached):");
        var result2 = await cachedService.GetDataAsync("SELECT * FROM Users");
        Console.WriteLine($"Result: {result2}");
        
        Console.WriteLine("\nDifferent query:");
        var result3 = await cachedService.GetDataAsync("SELECT * FROM Orders");
        Console.WriteLine($"Result: {result3}");
    }
}

8. Choosing the Right Structural Pattern

Pattern When to Use Key Benefit .NET Use Cases
Adapter Need to use existing class with incompatible interface Makes incompatible interfaces work together Database providers, API clients, third-party integrations
Bridge Want to avoid permanent binding between abstraction and implementation Decouples abstraction from implementation UI frameworks, device drivers, database drivers
Composite Need to treat individual objects and compositions uniformly Simplifies tree structure operations UI controls, file systems, organizational hierarchies
Decorator Need to add responsibilities dynamically without subclassing Flexible alternative to inheritance ASP.NET Core Middleware, streams, logging, caching
Facade Need to simplify a complex subsystem Provides simple interface to complex system Service layer, API gateways, libraries
Flyweight Need to create large number of similar objects Reduces memory usage Character rendering, game objects, connection pools
Proxy Need to control access to an object Adds level of indirection Lazy loading, caching, logging, access control, WCF remoting

9. Summary and Key Takeaways

  • Adapter: Converts one interface to another. Use when integrating with third-party code.
  • Bridge: Separates abstraction from implementation. Use when both can vary independently.
  • Composite: Treats individual objects and compositions uniformly. Use for tree structures.
  • Decorator: Adds responsibilities dynamically. Use for open/closed principle compliance.
  • Facade: Simplifies complex subsystems. Use to provide a clean API.
  • Flyweight: Shares objects to reduce memory. Use for large numbers of similar objects.
  • Proxy: Controls access to another object. Use for lazy loading, caching, logging, protection.

🎯 Design Tips:

  • Start with Facade when you need to simplify complex legacy code
  • Use Adapter when working with third-party libraries that you can't modify
  • Apply Decorator when you have many combinations of features
  • Consider Composite for any hierarchical data structure
  • Use Proxy when you need lazy initialization or access control
  • Apply Bridge when you anticipate changes in both abstraction and implementation
  • Use Flyweight only when you have proven memory issues with many objects

10. Practice Exercises

Exercise 1: Implement a Logging Adapter

Create an adapter that makes a third-party logging library work with your application's logging interface.

Click to see solution
// Your application's logging interface
public interface IAppLogger
{
    void Info(string message);
    void Warning(string message);
    void Error(string message, Exception ex = null);
}

// Third-party logger (cannot modify)
public class ThirdPartyLogger
{
    public void Write(LogLevel level, string message, string source = null)
    {
        Console.WriteLine($"[{level}] [{source}] {message}");
    }
}

public enum LogLevel
{
    Info,
    Warning,
    Error,
    Debug
}

// Adapter implementation
public class ThirdPartyLoggerAdapter : IAppLogger
{
    private readonly ThirdPartyLogger _logger;
    private readonly string _source;
    
    public ThirdPartyLoggerAdapter(ThirdPartyLogger logger, string source)
    {
        _logger = logger;
        _source = source;
    }
    
    public void Info(string message)
    {
        _logger.Write(LogLevel.Info, message, _source);
    }
    
    public void Warning(string message)
    {
        _logger.Write(LogLevel.Warning, message, _source);
    }
    
    public void Error(string message, Exception ex = null)
    {
        var fullMessage = ex != null ? $"{message}: {ex.Message}" : message;
        _logger.Write(LogLevel.Error, fullMessage, _source);
    }
}

Exercise 2: Create a Caching Proxy for a Weather Service

Implement a proxy that caches weather data for 5 minutes.

Click to see solution
public interface IWeatherService
{
    Task<WeatherData> GetWeatherAsync(string city);
}

public class WeatherData
{
    public string City { get; set; }
    public double Temperature { get; set; }
    public string Condition { get; set; }
    public DateTime RetrievedAt { get; set; }
}

public class RealWeatherService : IWeatherService
{
    private readonly Random _random = new Random();
    
    public async Task<WeatherData> GetWeatherAsync(string city)
    {
        Console.WriteLine($"Fetching weather for {city} from external API...");
        await Task.Delay(1500); // Simulate API call
        
        return new WeatherData
        {
            City = city,
            Temperature = _random.Next(-10, 40),
            Condition = _random.Next(0, 3) switch
            {
                0 => "Sunny",
                1 => "Cloudy",
                2 => "Rainy",
                _ => "Windy"
            },
            RetrievedAt = DateTime.Now
        };
    }
}

public class CachingWeatherProxy : IWeatherService
{
    private readonly IWeatherService _realService;
    private readonly Dictionary<string, (WeatherData Data, DateTime Expiry)> _cache;
    private readonly TimeSpan _cacheDuration;
    
    public CachingWeatherProxy(IWeatherService realService, TimeSpan cacheDuration)
    {
        _realService = realService;
        _cache = new Dictionary<string, (WeatherData, DateTime)>();
        _cacheDuration = cacheDuration;
    }
    
    public async Task<WeatherData> GetWeatherAsync(string city)
    {
        if (_cache.ContainsKey(city) && _cache[city].Expiry > DateTime.Now)
        {
            Console.WriteLine($"Returning cached weather for {city}");
            return _cache[city].Data;
        }
        
        var data = await _realService.GetWeatherAsync(city);
        _cache[city] = (data, DateTime.Now + _cacheDuration);
        return data;
    }
}

11. What's Next?

In Chapter 5, we'll explore Behavioral Design Patterns - Strategy, Observer, Chain of Responsibility, Command, State, Template Method, and Mediator. These patterns focus on communication between objects and the assignment of responsibilities.

Chapter 5 Preview:

  • Strategy - Encapsulate interchangeable algorithms
  • Observer - Define one-to-many dependencies
  • Chain of Responsibility - Pass requests along a chain of handlers
  • Command - Encapsulate requests as objects
  • State - Alter behavior when internal state changes
  • Template Method - Define algorithm skeleton, defer steps to subclasses
  • Mediator - Reduce coupling between objects

📝 Practice Assignments:

  1. Create an adapter for a payment gateway (Stripe/PayPal) to work with your existing payment interface
  2. Implement a remote control system using Bridge pattern with multiple device types and remote types
  3. Build a file system browser using Composite pattern
  4. Create a coffee ordering system with decorators for different add-ons (milk, sugar, syrup)
  5. Implement a simple ORM facade that hides complex database operations
  6. Create a flyweight for a game that needs to render thousands of trees or enemies
  7. Implement a caching proxy for a slow API service
  8. Combine multiple structural patterns to build a complete document processing 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.