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); } }