Use cases

  1. Singleton Pattern:

  • Real-life Example: Managing a configuration settings object for an application. There should only be one instance of the configuration settings to avoid inconsistencies.

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

public class ConfigurationManager
{
    private static ConfigurationManager _instance;
    private static readonly object _lock = new object();

    public string Setting { get; set; }

    private ConfigurationManager() { }

    public static ConfigurationManager Instance
    {
        get
        {
            lock (_lock)
            {
                if (_instance == null)
                {
                    _instance = new ConfigurationManager();
                }
                return _instance;
            }
        }
    }
}

// Usage
ConfigurationManager.Instance.Setting = "Some setting";
Console.WriteLine(ConfigurationManager.Instance.Setting);
  1. Observer Pattern:

  • Real-life Example: Notification systems. When an event (like an email received) occurs, all the subscribers (such as different modules of an application) are notified and updated automatically.

  • Explanation: Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

using System;
using System.Collections.Generic;

// Subject Interface
public interface IEmailSubject
{
    void RegisterObserver(IEmailObserver observer);
    void RemoveObserver(IEmailObserver observer);
    void NotifyObservers();
}

// Observer Interface
public interface IEmailObserver
{
    void Update(string message);
}

// Concrete Subject
public class EmailServer : IEmailSubject
{
    private List<IEmailObserver> observers;
    private string emailMessage;

    public EmailServer()
    {
        observers = new List<IEmailObserver>();
    }

    public string EmailMessage
    {
        get { return emailMessage; }
        set
        {
            emailMessage = value;
            NotifyObservers();
        }
    }

    public void RegisterObserver(IEmailObserver observer)
    {
        observers.Add(observer);
    }

    public void RemoveObserver(IEmailObserver observer)
    {
        observers.Remove(observer);
    }

    public void NotifyObservers()
    {
        foreach (var observer in observers)
        {
            observer.Update(emailMessage);
        }
    }
}

// Concrete Observers
public class MobileApp : IEmailObserver
{
    public void Update(string message)
    {
        Console.WriteLine("Mobile App Notification: " + message);
    }
}

public class DesktopApp : IEmailObserver
{
    public void Update(string message)
    {
        Console.WriteLine("Desktop App Notification: " + message);
    }
}

public class WebApp : IEmailObserver
{
    public void Update(string message)
    {
        Console.WriteLine("Web App Notification: " + message);
    }
}

// Program
class Program
{
    static void Main()
    {
        // Create a subject
        EmailServer emailServer = new EmailServer();

        // Create observers
        IEmailObserver mobileApp = new MobileApp();
        IEmailObserver desktopApp = new DesktopApp();
        IEmailObserver webApp = new WebApp();

        // Register observers
        emailServer.RegisterObserver(mobileApp);
        emailServer.RegisterObserver(desktopApp);
        emailServer.RegisterObserver(webApp);

        // Change the state of the subject
        emailServer.EmailMessage = "New Email: 'Observer Design Pattern in C#'";

        // Remove an observer
        emailServer.RemoveObserver(desktopApp);

        // Change the state of the subject again
        emailServer.EmailMessage = "New Email: 'Advanced C# Programming'";
    }
}
  1. Factory Pattern:

  • Real-life Example: Creating objects in a banking application for different types of accounts (e.g., savings, checking). The factory method can return different types of accounts based on the input provided.

  • Explanation: Defines an interface for creating an object, but lets subclasses alter the type of objects that will be created.

using System;

// Product: Account interface
public interface IAccount
{
    void Deposit(decimal amount);
    void Withdraw(decimal amount);
    decimal GetBalance();
}

// Concrete Product: Savings Account
public class SavingsAccount : IAccount
{
    private decimal _balance;

    public void Deposit(decimal amount)
    {
        _balance += amount;
        Console.WriteLine($"Deposited {amount:C}. Current balance: {_balance:C}");
    }

    public void Withdraw(decimal amount)
    {
        if (_balance >= amount)
        {
            _balance -= amount;
            Console.WriteLine($"Withdrawn {amount:C}. Current balance: {_balance:C}");
        }
        else
        {
            Console.WriteLine("Insufficient funds.");
        }
    }

    public decimal GetBalance()
    {
        return _balance;
    }
}

// Concrete Product: Checking Account
public class CheckingAccount : IAccount
{
    private decimal _balance;

    public void Deposit(decimal amount)
    {
        _balance += amount;
        Console.WriteLine($"Deposited {amount:C}. Current balance: {_balance:C}");
    }

    public void Withdraw(decimal amount)
    {
        if (_balance - amount >= 0)
        {
            _balance -= amount;
            Console.WriteLine($"Withdrawn {amount:C}. Current balance: {_balance:C}");
        }
        else
        {
            Console.WriteLine("Insufficient funds.");
        }
    }

    public decimal GetBalance()
    {
        return _balance;
    }
}

// Creator: AccountFactory interface
public interface IAccountFactory
{
    IAccount CreateAccount();
}

// Concrete Creator: Savings Account Factory
public class SavingsAccountFactory : IAccountFactory
{
    public IAccount CreateAccount()
    {
        return new SavingsAccount();
    }
}

// Concrete Creator: Checking Account Factory
public class CheckingAccountFactory : IAccountFactory
{
    public IAccount CreateAccount()
    {
        return new CheckingAccount();
    }
}

// Client code
public class Program
{
    public static void Main(string[] args)
    {
        // Creating a savings account
        IAccountFactory savingsFactory = new SavingsAccountFactory();
        IAccount savingsAccount = savingsFactory.CreateAccount();

        // Depositing and withdrawing from savings account
        savingsAccount.Deposit(1000);
        savingsAccount.Withdraw(200);

        // Creating a checking account
        IAccountFactory checkingFactory = new CheckingAccountFactory();
        IAccount checkingAccount = checkingFactory.CreateAccount();

        // Depositing and withdrawing from checking account
        checkingAccount.Deposit(500);
        checkingAccount.Withdraw(300);
    }
}
  1. Decorator Pattern:

  • Real-life Example: Adding functionality to a user interface component in a graphical application. For instance, adding scrollbars to a window.

  • Explanation: Allows behavior to be added to individual objects, dynamically, without affecting the behavior of other objects from the same class.

  1. Strategy Pattern:

  • Real-life Example: Different algorithms for sorting data (e.g., quicksort, mergesort, bubblesort). The strategy pattern allows the selection of the algorithm at runtime.

  • Explanation: Defines a family of algorithms, encapsulates each one, and makes them interchangeable. This pattern lets the algorithm vary independently from clients that use it.

using System;

// Strategy Interface
public interface IPaymentStrategy
{
    void Pay(decimal amount);
}

// Concrete Strategies
public class CreditCardPayment : IPaymentStrategy
{
    private string cardNumber;

    public CreditCardPayment(string cardNumber)
    {
        this.cardNumber = cardNumber;
    }

    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paying {amount:C} using Credit Card (Number: {cardNumber}).");
    }
}

public class PayPalPayment : IPaymentStrategy
{
    private string email;

    public PayPalPayment(string email)
    {
        this.email = email;
    }

    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paying {amount:C} using PayPal (Email: {email}).");
    }
}

public class CryptoPayment : IPaymentStrategy
{
    private string walletAddress;

    public CryptoPayment(string walletAddress)
    {
        this.walletAddress = walletAddress;
    }

    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paying {amount:C} using Cryptocurrency (Wallet Address: {walletAddress}).");
    }
}

// Context
public class PaymentContext
{
    private IPaymentStrategy paymentStrategy;

    public void SetPaymentStrategy(IPaymentStrategy strategy)
    {
        this.paymentStrategy = strategy;
    }

    public void ProcessPayment(decimal amount)
    {
        if (paymentStrategy == null)
        {
            throw new InvalidOperationException("Payment strategy is not set.");
        }

        paymentStrategy.Pay(amount);
    }
}

// Program
class Program
{
    static void Main()
    {
        PaymentContext paymentContext = new PaymentContext();

        // Pay using Credit Card
        paymentContext.SetPaymentStrategy(new CreditCardPayment("1234-5678-9876-5432"));
        paymentContext.ProcessPayment(120.00m);

        // Pay using PayPal
        paymentContext.SetPaymentStrategy(new PayPalPayment("user@example.com"));
        paymentContext.ProcessPayment(75.50m);

        // Pay using Cryptocurrency
        paymentContext.SetPaymentStrategy(new CryptoPayment("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"));
        paymentContext.ProcessPayment(250.00m);
    }
}
  1. Command Pattern:

  • Real-life Example: Implementing undo functionality in a text editor. Each action (like typing or deleting) is encapsulated as an object that can be stored and executed.

  • Explanation: Encapsulates a request as an object, thereby allowing for parameterization of clients with queues, requests, and operations.

  1. Adapter Pattern:

  • Real-life Example: Integrating with a third-party library where the interface does not match the rest of your application. An adapter can translate calls from your application to the third-party library. Payment gateway third party

  • Explanation: Allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code.

using System;

namespace AdapterPattern
{
    // Target Interface
    public interface IPaymentGateway
    {
        void ProcessPayment(string cardNumber, string cardHolderName, DateTime expiryDate, decimal amount);
    }

    // Adaptee: Third-party payment gateway
    public class ThirdPartyPaymentGateway
    {
        public void Pay(string cardNumber, string cardHolder, string expiry, decimal amount)
        {
            Console.WriteLine($"Payment of {amount:C} processed by ThirdPartyPaymentGateway for card {cardNumber}.");
        }
    }

    // Adapter: Adapts ThirdPartyPaymentGateway to IPaymentGateway
    public class PaymentGatewayAdapter : IPaymentGateway
    {
        private readonly ThirdPartyPaymentGateway _thirdPartyPaymentGateway;

        public PaymentGatewayAdapter(ThirdPartyPaymentGateway thirdPartyPaymentGateway)
        {
            _thirdPartyPaymentGateway = thirdPartyPaymentGateway;
        }

        public void ProcessPayment(string cardNumber, string cardHolderName, DateTime expiryDate, decimal amount)
        {
            // Convert expiryDate to string format expected by the third-party gateway
            string expiry = expiryDate.ToString("MM/yy");
            _thirdPartyPaymentGateway.Pay(cardNumber, cardHolderName, expiry, amount);
        }
    }

    // Client code
    class Program
    {
        static void Main(string[] args)
        {
            // Create an instance of the third-party payment gateway
            ThirdPartyPaymentGateway thirdPartyPaymentGateway = new ThirdPartyPaymentGateway();

            // Create an instance of the adapter
            IPaymentGateway paymentGateway = new PaymentGatewayAdapter(thirdPartyPaymentGateway);

            // Use the payment gateway via the adapter
            paymentGateway.ProcessPayment("1234-5678-9012-3456", "John Doe", new DateTime(2025, 12, 31), 100.00m);
            // Output: Payment of $100.00 processed by ThirdPartyPaymentGateway for card 1234-5678-9012-3456.
        }
    }
}
  1. Prototype Pattern:

  • Real-life Example: Creating objects in a graphic application (e.g., shapes, drawings). Instead of creating new instances from scratch, a prototype pattern clones existing objects.

  • Explanation: Specifies the kind of objects to create using a prototypical instance, and creates new objects by copying this prototype.

  1. Builder Pattern:

    • Real-life Example: Constructing complex objects such as vehicles, where different parts like engine, wheels, and body can be configured step-by-step.

    • Explanation: Separates the construction of a complex object from its representation so that the same construction process can create different representations.

  2. Facade Pattern:

    • Real-life Example: Simplifying interactions with a complex library or framework (e.g., a library for 3D graphics). A facade provides a simplified interface to the complex subsystems. Banking system

    • Explanation: Provides a unified interface to a set of interfaces in a subsystem, making the subsystem easier to use.

using System;

namespace FacadePattern
{
    // Subsystem Class 1
    public class AccountService
    {
        public void GetAccountDetails(string accountId)
        {
            Console.WriteLine($"Fetching account details for account ID: {accountId}");
        }
    }

    // Subsystem Class 2
    public class TransferService
    {
        public void TransferAmount(string fromAccount, string toAccount, decimal amount)
        {
            Console.WriteLine($"Transferring {amount:C} from {fromAccount} to {toAccount}");
        }
    }

    // Subsystem Class 3
    public class LoanService
    {
        public void ApplyForLoan(string accountId, decimal loanAmount)
        {
            Console.WriteLine($"Applying for a loan of {loanAmount:C} for account ID: {accountId}");
        }
    }

    // Facade Class
    public class BankingFacade
    {
        private readonly AccountService _accountService;
        private readonly TransferService _transferService;
        private readonly LoanService _loanService;

        public BankingFacade()
        {
            _accountService = new AccountService();
            _transferService = new TransferService();
            _loanService = new LoanService();
        }

        public void GetAccountDetails(string accountId)
        {
            _accountService.GetAccountDetails(accountId);
        }

        public void TransferAmount(string fromAccount, string toAccount, decimal amount)
        {
            _transferService.TransferAmount(fromAccount, toAccount, amount);
        }

        public void ApplyForLoan(string accountId, decimal loanAmount)
        {
            _loanService.ApplyForLoan(accountId, loanAmount);
        }
    }

    // Client code
    class Program
    {
        static void Main(string[] args)
        {
            BankingFacade bankingFacade = new BankingFacade();

            // Use the facade to perform various operations
            bankingFacade.GetAccountDetails("12345");
            bankingFacade.TransferAmount("12345", "67890", 500.00m);
            bankingFacade.ApplyForLoan("12345", 10000.00m);
        }
    }
}
  1. Proxy Pattern:

    • Real-life Example: Accessing a remote service or resource. A proxy can control access to the original object, allowing additional functionalities like lazy initialization, logging, or access control.

    • Explanation: Provides a surrogate or placeholder for another object to control access to it.

  2. Chain of Responsibility Pattern:

    • Real-life Example: Support Ticket Processing System. Imagine a support ticket system where a customer support request could be handled by multiple levels of support staff: Level 1, Level 2, and Level 3 support. Each level of support will try to handle the request, and if it cannot, it will pass the request to the next level.

    • Explanation: Passes a request along a chain of handlers. Each handler decides either to process the request or to pass it to the next handler in the chain.

using System;

namespace ChainOfResponsibilityExample
{
    // Enum for issue severity
    public enum IssueSeverity
    {
        Low,
        Medium,
        High
    }

    // Support ticket class
    public class SupportTicket
    {
        public IssueSeverity IssueSeverity { get; set; }
        public string Description { get; set; }

        public SupportTicket(IssueSeverity severity, string description)
        {
            IssueSeverity = severity;
            Description = description;
        }
    }

    // Abstract handler
    public abstract class SupportHandler
    {
        protected SupportHandler _nextHandler;

        public void SetNextHandler(SupportHandler nextHandler)
        {
            _nextHandler = nextHandler;
        }

        public abstract void HandleRequest(SupportTicket ticket);
    }

    // Concrete handler for Level 1 support
    public class Level1SupportHandler : SupportHandler
    {
        public override void HandleRequest(SupportTicket ticket)
        {
            if (ticket.IssueSeverity == IssueSeverity.Low)
            {
                Console.WriteLine("Level 1 Support: Handling low severity issue.");
            }
            else if (_nextHandler != null)
            {
                _nextHandler.HandleRequest(ticket);
            }
        }
    }

    // Concrete handler for Level 2 support
    public class Level2SupportHandler : SupportHandler
    {
        public override void HandleRequest(SupportTicket ticket)
        {
            if (ticket.IssueSeverity == IssueSeverity.Medium)
            {
                Console.WriteLine("Level 2 Support: Handling medium severity issue.");
            }
            else if (_nextHandler != null)
            {
                _nextHandler.HandleRequest(ticket);
            }
        }
    }

    // Concrete handler for Level 3 support
    public class Level3SupportHandler : SupportHandler
    {
        public override void HandleRequest(SupportTicket ticket)
        {
            if (ticket.IssueSeverity == IssueSeverity.High)
            {
                Console.WriteLine("Level 3 Support: Handling high severity issue.");
            }
            else
            {
                Console.WriteLine("Issue could not be handled.");
            }
        }
    }

    // Main program
    class Program
    {
        static void Main(string[] args)
        {
            // Create handlers
            var level1 = new Level1SupportHandler();
            var level2 = new Level2SupportHandler();
            var level3 = new Level3SupportHandler();

            // Set up the chain of responsibility
            level1.SetNextHandler(level2);
            level2.SetNextHandler(level3);

            // Create support tickets
            var ticket1 = new SupportTicket(IssueSeverity.Low, "Password reset");
            var ticket2 = new SupportTicket(IssueSeverity.Medium, "Unable to access account");
            var ticket3 = new SupportTicket(IssueSeverity.High, "System outage");

            // Process the tickets
            level1.HandleRequest(ticket1);
            level1.HandleRequest(ticket2);
            level1.HandleRequest(ticket3);
        }
    }
}
  1. Composite Pattern:

    • Real-life Example: Representing a hierarchy of graphical objects, where individual objects (e.g., lines, circles) and groups of objects are treated uniformly.

    • Explanation: Composes objects into tree structures to represent part-whole hierarchies, allowing clients to treat individual objects and compositions uniformly.

  2. Flyweight Pattern:

    • Real-life Example: Managing a large number of fine-grained objects efficiently, such as characters in a text editor. Flyweight minimizes memory usage by sharing as much data as possible.

    • Explanation: Uses sharing to support large numbers of fine-grained objects efficiently.

  3. State Pattern:

    • Real-life Example: A vending machine that changes its behavior based on the current state (e.g., no coin inserted, coin inserted, dispensing product).

    • Explanation: Allows an object to alter its behavior when its internal state changes, making the object appear to change its class.

  4. Memento Pattern:

    • Real-life Example: Implementing undo functionality in applications. A memento captures and stores the current state of an object so it can be restored later.

    • Explanation: Captures and externalizes an object's internal state so that it can be restored later without violating encapsulation.

  5. Template Method Pattern:

    • Real-life Example: Defining the skeleton of an algorithm in a method in a base class, leaving the implementation of some steps to subclasses. For example, a base class providing a template for document generation with specific steps for different document formats.

    • Explanation: Defines the skeleton of an algorithm in an operation, deferring some steps to subclasses.

  6. Visitor Pattern:

    • Real-life Example: Performing operations on a collection of objects with different types (e.g., a compiler performing type checking, optimization, and code generation on an abstract syntax tree).

    • Explanation: Represents an operation to be performed on elements of an object structure, allowing new operations to be defined without changing the classes of the elements on which it operates.

  7. Mediator Pattern:

    • Real-life Example: Managing interactions between multiple components in a user interface. Instead of each component interacting directly with every other component, a mediator handles the communication, reducing dependencies. Chat Room example

    • Explanation: Defines an object that encapsulates how a set of objects interact, promoting loose coupling by keeping objects from referring to each other explicitly.

using System;
using System.Collections.Generic;

// Mediator Interface
public interface IChatRoomMediator
{
    void SendMessage(string message, User user);
    void RegisterUser(User user);
}

// Concrete Mediator
public class ChatRoom : IChatRoomMediator
{
    private readonly Dictionary<string, User> _users = new Dictionary<string, User>();

    public void RegisterUser(User user)
    {
        if (!_users.ContainsKey(user.Name))
        {
            _users[user.Name] = user;
        }

        user.SetChatRoom(this);
    }

    public void SendMessage(string message, User user)
    {
        foreach (var u in _users.Values)
        {
            // Don't send the message to the sender
            if (u != user)
            {
                u.Receive(message, user.Name);
            }
        }
    }
}

// Colleague Interface
public abstract class User
{
    protected IChatRoomMediator _chatRoom;
    public string Name { get; private set; }

    public User(string name)
    {
        Name = name;
    }

    public void SetChatRoom(IChatRoomMediator chatRoom)
    {
        _chatRoom = chatRoom;
    }

    public void Send(string message)
    {
        Console.WriteLine($"{Name} sends: {message}");
        _chatRoom.SendMessage(message, this);
    }

    public abstract void Receive(string message, string from);
}

// Concrete Colleague
public class ChatUser : User
{
    public ChatUser(string name) : base(name) { }

    public override void Receive(string message, string from)
    {
        Console.WriteLine($"{Name} received from {from}: {message}");
    }
}

// Client Code
public class Program
{
    public static void Main(string[] args)
    {
        IChatRoomMediator chatRoom = new ChatRoom();

        User user1 = new ChatUser("Alice");
        User user2 = new ChatUser("Bob");
        User user3 = new ChatUser("Charlie");

        chatRoom.RegisterUser(user1);
        chatRoom.RegisterUser(user2);
        chatRoom.RegisterUser(user3);

        user1.Send("Hello, everyone!");
        user2.Send("Hi Alice!");
        user3.Send("Hey Alice and Bob!");
    }
}

Last updated