Understanding Design Patterns

Design Patterns Overview

Design patterns are typical solutions to common problems in software design. Each pattern is like a blueprint you can customize to solve a particular design problem in your code.

Categories of Design Patterns

  • Creational Patterns: These patterns provide various object creation mechanisms, which increase the flexibility and reuse of existing code.
  • Structural Patterns: These patterns explain how to assemble objects and classes into larger structures, while keeping the structures flexible and efficient.
  • Behavioral Patterns: These patterns are concerned with algorithms and the assignment of responsibilities between objects.

Creational Patterns

  • Singleton: Ensures that a class has only one instance, and provides a global point of access to it.
    C#
                          public class Singleton {
                              private static Singleton instance;
                              private Singleton() {}
                              public static Singleton Instance {
                                  get {
                                      if (instance == null) {
                                          instance = new Singleton();
                                      }
                                      return instance;
                                  }
                              }
                          }
                      
  • Factory Method: Defines an interface for creating an object, but lets subclasses decide which class to instantiate.
    C#
                          public abstract class Creator {
                              public abstract Product FactoryMethod();
                          }
                          public class ConcreteCreator : Creator {
                              public override Product FactoryMethod() { return new ConcreteProduct(); }
                          }
                      
  • Abstract Factory: Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
    C#
                          interface IFactory {
                              IProductA CreateProductA();
                              IProductB CreateProductB();
                          }
                          class ConcreteFactory1 : IFactory {
                              public IProductA CreateProductA() { return new ProductA1(); }
                              public IProductB CreateProductB() { return new ProductB1(); }
                          }
                      
  • Builder: Separates the construction of a complex object from its representation.
    C#
                          class Director {
                              public void Construct(Builder builder) {
                                  builder.BuildPartA();
                                  builder.BuildPartB();
                              }
                          }
                      
  • Prototype: Specifies the kinds of objects to create using a prototypical instance.
    C#
                          class Prototype {
                              public int X { get; private set; }
                              public Prototype Clone() {
                                  return (Prototype)this.MemberwiseClone();
                              }
                          }
                      
  • Object Pool: Avoids expensive acquisition and release of resources by recycling objects that are no longer in use.
    C#
                          public class ObjectPool {
                              private List<Reusable> available = new List<Reusable>();
                              public Reusable GetReusable() {
                                  if (available.Count > 0) {
                                      Reusable r = available[0];
                                      available.RemoveAt(0);
                                      return r;
                                  } else {
                                      return new Reusable();
                                  }
                              }
                          }
                      

Structural Patterns

  • Adapter: Allows objects with incompatible interfaces to work together by wrapping their interfaces.
    C#
                // Old Printer Interface (Adaptee)
                public interface IOldPrinter {
                    void Print(string text);
                }
    
                // Old Printer Implementation
                public class OldPrinter : IOldPrinter {
                    public void Print(string text) {
                        Console.WriteLine($"Old Printer: {text}");
                    }
                }
    
                // Modern Printer Interface (Target)
                public interface IModernPrinter {
                    void PrintDocument(string document);
                }
    
                public class ModernPrinter : IModernPrinter {
                    public void PrintDocument(string document) {
                        Console.WriteLine($"Modern Printer: {document}");
                    }
                }
    
                // Adapter Class
                public class PrinterAdapter : IModernPrinter {
                    private IOldPrinter oldPrinter;
    
                    public PrinterAdapter(IOldPrinter oldPrinter) {
                        this.oldPrinter = oldPrinter;
                    }
    
                    public void PrintDocument(string document) {
                        oldPrinter.Print(document);
                    }
                }
            
  • Bridge: Decouples an abstraction from its implementation so that the two can vary independently.
    C#
                // Implementor
                public interface IImplementor {
                    void OperationImpl();
                }
    
                // ConcreteImplementorA
                public class ConcreteImplementorA : IImplementor {
                    public void OperationImpl() {
                        Console.WriteLine("ConcreteImplementorA Operation");
                    }
                }
    
                // ConcreteImplementorB
                public class ConcreteImplementorB : IImplementor {
                    public void OperationImpl() {
                        Console.WriteLine("ConcreteImplementorB Operation");
                    }
                }
    
                // Abstraction
                public abstract class Abstraction {
                    protected IImplementor implementor;
                    public Abstraction(IImplementor impl) {
                        implementor = impl;
                    }
                    public virtual void Operation() {
                        implementor.OperationImpl();
                    }
                }
    
                // RefinedAbstraction
                public class RefinedAbstraction : Abstraction {
                    public RefinedAbstraction(IImplementor impl) : base(impl) { }
                    public override void Operation() {
                        Console.WriteLine("RefinedAbstraction Operation");
                        implementor.OperationImpl();
                    }
                }
            
  • Composite: Composes objects into tree structures to represent part-whole hierarchies.
    C#
                // Component
                public interface IComponent {
                    void Operation();
                }
    
                // Leaf
                public class Leaf : IComponent {
                    public void Operation() {
                        Console.WriteLine("Leaf operation");
                    }
                }
    
                // Composite
                public class Composite : IComponent {
                    private List<IComponent> children = new List<IComponent>();
    
                    public void Add(IComponent component) {
                        children.Add(component);
                    }
    
                    public void Operation() {
                        Console.WriteLine("Composite Operation");
                        foreach (var child in children) {
                            child.Operation();
                        }
                    }
                }
            
  • Decorator: Allows for dynamically adding behavior to an object.
    C#
                // Component
                public interface ICoffee {
                    double GetCost();
                    string GetDescription();
                }
    
                // ConcreteComponent
                public class SimpleCoffee : ICoffee {
                    public double GetCost() {
                        return 1.0;
                    }
                    public string GetDescription() {
                        return "Simple coffee";
                    }
                }
    
                // Decorator
                public abstract class CoffeeDecorator : ICoffee {
                    protected ICoffee coffee;
                    public CoffeeDecorator(ICoffee coffee) {
                        this.coffee = coffee;
                    }
                    public virtual double GetCost() {
                        return coffee.GetCost();
                    }
                    public virtual string GetDescription() {
                        return coffee.GetDescription();
                    }
                }
    
                // ConcreteDecorator
                public class MilkDecorator : CoffeeDecorator {
                    public MilkDecorator(ICoffee coffee) : base(coffee) { }
                    public override double GetCost() {
                        return base.GetCost() + 0.5;
                    }
                    public override string GetDescription() {
                        return base.GetDescription() + ", milk";
                    }
                }
            
  • Facade: Provides a simplified interface to a complex system.
    C#
                // Subsystems
                public class SubsystemA {
                    public void A1() { }
                }
                public class SubsystemB {
                    public void B1() { }
                }
    
                // Facade
                public class Facade {
                    private SubsystemA a = new SubsystemA();
                    private SubsystemB b = new SubsystemB();
    
                    public void Operation1() {
                        a.A1();
                        b.B1();
                    }
                }
            
  • Flyweight: Minimizes memory usage by sharing as much data as possible with similar objects.
    C#
                // Flyweight
                public interface IFlyweight {
                    void Operation(int extrinsicState);
                }
    
                // ConcreteFlyweight
                public class ConcreteFlyweight : IFlyweight {
                    int intrinsicState;
    
                    public void Operation(int extrinsicState) {
                        Console.WriteLine("ConcreteFlyweight: " + (intrinsicState + extrinsicState));
                    }
                }
    
                // FlyweightFactory
                public class FlyweightFactory {
                    private Dictionary<string, IFlyweight> flyweights = new Dictionary<string, IFlyweight>();
    
                    public IFlyweight GetFlyweight(string key) {
                        if (!flyweights.ContainsKey(key)) {
                            flyweights[key] = new ConcreteFlyweight();
                        }
                        return flyweights[key];
                    }
                }
            
  • Proxy: Provides a surrogate or placeholder for another object to control access to it.
    C#
                // Subject Interface
                public interface ISubject {
                    void Request();
                }
    
                // RealSubject
                public class RealSubject : ISubject {
                    public void Request() {
                        Console.WriteLine("RealSubject Request");
                    }
                }
    
                // Proxy
                public class Proxy : ISubject {
                    private RealSubject realSubject;
    
                    public void Request() {
                        if (realSubject == null) {
                            realSubject = new RealSubject();
                        }
                        realSubject.Request();
                    }
                }
            

Behavioral Patterns

  • Observer: Allows objects to notify changes to multiple other objects.
    C#
                // Subject
                public interface ISubject {
                    void Attach(IObserver observer);
                    void Detach(IObserver observer);
                    void Notify();
                }
    
                // Concrete Subject
                public class ConcreteSubject : ISubject {
                    private List<IObserver> observers = new List<IObserver>();
                    public void Attach(IObserver observer) {
                        observers.Add(observer);
                    }
                    public void Detach(IObserver observer) {
                        observers.Remove(observer);
                    }
                    public void Notify() {
                        foreach (var observer in observers) {
                            observer.Update();
                        }
                    }
                }
    
                // Observer
                public interface IObserver {
                    void Update();
                }
    
                // Concrete Observer
                public class ConcreteObserver : IObserver {
                    public void Update() {
                        Console.WriteLine("Observer updated");
                    }
                }
            
  • Chain of Responsibility: Passes a request along a chain of potential handlers.
    C#
                // Handler
                public abstract class Handler {
                    protected Handler successor;
                    public void SetSuccessor(Handler successor) {
                        this.successor = successor;
                    }
                    public abstract void HandleRequest(int condition);
                }
    
                // Concrete Handler 1
                public class ConcreteHandler1 : Handler {
                    public override void HandleRequest(int condition) {
                        if (condition == 1) {
                            Console.WriteLine("Handled by ConcreteHandler1");
                        } else if (successor != null) {
                            successor.HandleRequest(condition);
                        }
                    }
                }
    
                // Concrete Handler 2
                public class ConcreteHandler2 : Handler {
                    public override void HandleRequest(int condition) {
                        if (condition == 2) {
                            Console.WriteLine("Handled by ConcreteHandler2");
                        } else if (successor != null) {
                            successor.HandleRequest(condition);
                        }
                    }
                }
            
  • Command: Encapsulates a command request as an object.
    C#
                // Command
                public interface ICommand {
                    void Execute();
                }
    
                // Concrete Command
                public class TurnOnCommand : ICommand {
                    private Light light;
                    public TurnOnCommand(Light light) {
                        this.light = light;
                    }
                    public void Execute() {
                        light.TurnOn();
                    }
                }
    
                // Receiver
                public class Light {
                    public void TurnOn() {
                        Console.WriteLine("Light turned on");
                    }
                }
    
                // Invoker
                public class RemoteControl {
                    private ICommand command;
                    public void SetCommand(ICommand command) {
                        this.command = command;
                    }
                    public void PressButton() {
                        command.Execute();
                    }
                }
            
  • State: Allows an object to alter its behavior when its internal state changes.
    C#
                // Context
                public class Context {
                    private State state;
                    public Context(State state) {
                        this.state = state;
                    }
                    public void Request() {
                        state.Handle(this);
                    }
                    public void SetState(State state) {
                        this.state = state;
                    }
                }
    
                // State
                public abstract class State {
                    public abstract void Handle(Context context);
                }
    
                // Concrete State A
                public class ConcreteStateA : State {
                    public override void Handle(Context context) {
                        Console.WriteLine("Handling state A");
                        context.SetState(new ConcreteStateB());
                    }
                }
    
                // Concrete State B
                public class ConcreteStateB : State {
                    public override void Handle(Context context) {
                        Console.WriteLine("Handling state B");
                        context.SetState(new ConcreteStateA());
                    }
                }
            
  • Strategy: Enables an algorithm's behavior to be selected at runtime.
    C#
                // Strategy
                public interface IStrategy {
                    void AlgorithmInterface();
                }
    
                // ConcreteStrategy A
                public class ConcreteStrategyA : IStrategy {
                    public void AlgorithmInterface() {
                        Console.WriteLine("Algorithm A");
                    }
                }
    
                // ConcreteStrategy B
                public class ConcreteStrategyB : IStrategy {
                    public void AlgorithmInterface() {
                        Console.WriteLine("Algorithm B");
                    }
                }
    
                // Context
                public class Context {
                    private IStrategy strategy;
                    public Context(IStrategy strategy) {
                        this.strategy = strategy;
                    }
                    public void ExecuteStrategy() {
                        strategy.AlgorithmInterface();
                    }
                }
            
  • Memento: Provides a way to restore an object to its previous state.
    C#
                // Originator
                public class Originator {
                    private string state;
                    public void SetState(string state) {
                        this.state = state;
                    }
                    public Memento SaveStateToMemento() {
                        return new Memento(state);
                    }
                    public void GetStateFromMemento(Memento memento) {
                        state = memento.GetState();
                    }
                }
    
                // Memento
                public class Memento {
                    private string state;
                    public Memento(string state) {
                        this.state = state;
                    }
                    public string GetState() {
                        return state;
                    }
                }
    
                // Caretaker
                public class Caretaker {
                    private List<Memento> mementoList = new List<Memento>();
                    public void Add(Memento state) {
                        mementoList.Add(state);
                    }
                    public Memento Get(int index) {
                        return mementoList[index];
                    }
                }
            
  • Visitor: Separates an algorithm from the objects on which it operates.
    C#
                // Visitor
                public interface IVisitor {
                    void VisitConcreteElementA(ConcreteElementA concreteElementA);
                    void VisitConcreteElementB(ConcreteElementB concreteElementB);
                }
    
                // ConcreteVisitor
                public class ConcreteVisitor : IVisitor {
                    public void VisitConcreteElementA(ConcreteElementA concreteElementA) {
                        Console.WriteLine("Visited ConcreteElementA");
                    }
                    public void VisitConcreteElementB(ConcreteElementB concreteElementB) {
                        Console.WriteLine("Visited ConcreteElementB");
                    }
                }
    
                // Element
                public interface IElement {
                    void Accept(IVisitor visitor);
                }
    
                // ConcreteElement A
                public class ConcreteElementA : IElement {
                    public void Accept(IVisitor visitor) {
                        visitor.VisitConcreteElementA(this);
                    }
                }
    
                // ConcreteElement B
                public class ConcreteElementB : IElement {
                    public void Accept(IVisitor visitor) {
                        visitor.VisitConcreteElementB(this);
                    }
                }